web-dev-qa-db-ja.com

Node.js非同期ライブラリの比較-Qと非同期

私は kriskowalのQライブラリ をプロジェクト(Webスクレイパー/ヒューマンアクティビティシミュレーター)に使用し、Promise、それらを返し、解決/拒否すること、およびライブラリの基本的な非同期制御フローメソッドとエラーについて知りました-スロー/キャッチメカニズムが不可欠であることが証明されています。

しかし、私はいくつかの問題に遭遇しました。私のpromise.then呼び出しと私のコールバックには、ピラミッドを形成するという不思議な傾向があります。スコープの理由による場合もあれば、特定の順序のイベントを保証する場合もあります。 (私はリファクタリングによってこれらの問題のいくつかを修正できると思いますが、今後は「コールバック地獄」を完全に避けたいと思います。)

また、デバッグは非常にイライラします。私は多くの時間をconsole.log-エラーとバグの発生源にたどり着きます。最終的にそれらを見つけたら、promise.finallyを使用してエラーをスローし、どこか他の場所でキャッチしますが、最初にエラーを特定するプロセスは困難です。

また、私のプロジェクトでは、順序が重要です。ほとんどすべてを順次実行する必要があります。プロミスを返す関数の配列を生成し、Array.prototype.reduceを使用してそれらを相互にチェーンしていることがよくありますが、これを行う必要はないと思います。

この削減手法を使用する私の方法の1つの例を次に示します。

removeItem: function (itemId) {

  var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);

  return this.getPage('/stock.php')
  .then(function (webpage) {
    var
      pageCount = 5,
      promiseFunctions = [],
      promiseSequence;

    // Create an array of promise-yielding functions that can run sequentially.
    _.times(pageCount, function (i) {
      var promiseFunction = function () {
        var
          promise,
          path;

        if (i === 0) {
          promise = Q(webpage);
        } else {
          path = '/stock.php?p=' + i;
          promise = this.getPage(path);
        }

        return promise.then(function (webpage) {
          var
            removeMatch = webpage.match(removeRegexp),
            removePath;

          if (removeMatch !== null) {
            removePath = removeitemMatch[0];

            return this.getPage(removePath)
            .delay(1000)
            // Stop calling subsequent promises.
            .thenResolve(true);
          }

          // Don't stop calling subsequent promises.
          return false;

        }.bind(this));
      }.bind(this);

      promiseFunctions.Push(promiseFunction);
    }, this);

    // Resolve the promises sequentially but stop early if the item is found.
    promiseSequence = promiseFunctions.reduce(function (soFar, promiseFunction, index) {
      return soFar.then(function (stop) {
        if (stop) {
          return true;
        } else {
          return Q.delay(1000).then(promiseFunction);
        }
      });
    }, Q());

    return promiseSequence;
  }.bind(this))
  .fail(function (onRejected) {
    console.log(onRejected);
  });
},

私は基本的に同じことをする他の方法がありますが、それははるかに悪いインデントの問題に苦しんでいます。

coalanの非同期ライブラリ を使用してプロジェクトをリファクタリングすることを検討しています。 Qと似ているようですが、どう違うのか正確に知りたいです。私が得ている印象は、Qが「約束中心」であるのに対し、非同期は「コールバック中心」であるということです。

質問:私の問題とプロジェクトの要件を考えると、Q over asyncを使用することで何が得られ、何が失われますか?ライブラリはどのように比較されますか? (特に、一連のタスクを順番に実行し、デバッグ/エラー処理を行うという点では?)

28
Jackson

どちらのライブラリも優れています。私はそれらが別々の目的を果たし、タンデムで使用できることを発見しました。

Qは、将来の値の表現であるpromiseオブジェクトを開発者に提供します。時間旅行に便利です。

Asyncは、制御構造と集計操作の非同期バージョンを開発者に提供します。

リンター実装の1つの試みの例は、ライブラリ間の潜在的な単一性を示しています。

function lint(files, callback) {

    // Function which returns a promise.
    var getMerged = merger('.jslintrc'),

        // Result objects to invoke callback with.
        results = [];

    async.each(files, function (file, callback) {
        fs.exists(file, function (exists) {

            // Future representation of the file's contents.
            var contentsPromise,

                // Future representation of JSLINT options from .jslintrc files.
                optionPromise;

            if (!exists) {
                callback();
                return;
            }

            contentsPromise = q.nfcall(fs.readFile, file, 'utf8');
            optionPromise = getMerged(path.dirname(file));

            // Parallelize IO operations.
            q.all([contentsPromise, optionPromise])
                .spread(function (contents, option) {
                    var success = JSLINT(contents, option),
                        errors,
                        fileResults;
                    if (!success) {
                        errors = JSLINT.data().errors;
                        fileResults = errors.reduce(function (soFar, error) {
                            if (error === null) {
                                return soFar;
                            }
                            return soFar.concat({
                                file: file,
                                error: error
                            });
                        }, []);
                        results = results.concat(fileResults);
                    }
                    process.nextTick(callback);
                })
                .catch(function (error) {
                    process.nextTick(function () {
                        callback(error);
                    });
                })
                .done();
        });
    }, function (error) {
        results = results.sort(function (a, b) {
            return a.file.charCodeAt(0) - b.file.charCodeAt(0);
        });
        callback(error, results);
    });
}

ファイルごとに潜在的にブロックすることをしたいのですが。そう async.eachは当然の選択です。並列化できます関連演算反復ごとq.allおよび2つ以上のファイルに適用される場合、オプション値を再利用します。

ここで、AsyncとQはそれぞれプログラムの制御フローに影響を与え、Qは将来いつかファイルの内容に解決される値を表します。ライブラリは連携して機能します。 「どちらか一方を選択する」必要はありません。

18
Jackson

コード内のコールバックピラミッドは、promise構成とJavaScriptの字句スコープを使用して簡略化できます。

removeItem: function (itemId) {

  var removeRegexp = new RegExp('\\/stock\\.php\\?remove=' + itemId);
  var found = false
  var promise = getPage('/sock.php')

  _.times(5, (i) => {
    promise = promise.then((webpage) => {
      if (found) return true
      var removeMatch = webpage.match(removeRegexp)
      var found = removeMath !== null
      var nextPage = found ? removeMatch[0] : '/stock.php?p='+i+1
      return Q.delay(1000).then(() => this.getPage(nextPage))
    })
  })

  return promise.fail(console.log.bind(console))

},

IMHO asyncは、新しいJavaScriptコードでは使用しないでください。 Promiseはより構成可能で、より直感的なコードを可能にします。

Nodeがpromiseを使用しなかった主な理由は、BluebirdやQなどのライブラリーによって十分に対処されてきたパフォーマンスの問題が原因でした。

Async/await構文が主流になるにつれて、Promiseは同期コードと非常によく似たコードへの道を開きます。

2
lorefnon