web-dev-qa-db-ja.com

Qで可変数のプロミスを順番にチェーンする方法は?

私は Qで任意の数のプロミスをチェーニングしました ;私の質問は違います。

可変数の呼び出しを行う方法を教えてください。呼び出しはそれぞれ非同期で戻ります。
シナリオはHTTPリクエストのセットであり、その数とタイプは最初のHTTPリクエストの結果によって決定されます。

簡単にやってみたいです。

この答え も見て、これは次のようなことを示唆しています:

var q = require('q'),
    itemsToProcess =  ["one", "two", "three", "four", "five"];

function getDeferredResult(prevResult) {
  return (function (someResult) {
    var deferred = q.defer();
    // any async function (setTimeout for now will do, $.ajax() later)
    setTimeout(function () {
      var nextResult = (someResult || "Initial_Blank_Value ") + ".." + itemsToProcess[0];
      itemsToProcess = itemsToProcess.splice(1);
      console.log("tick", nextResult, "Array:", itemsToProcess);
      deferred.resolve(nextResult);
    }, 600);

    return deferred.promise;
  }(prevResult));
}

var chain = q.resolve("start");
for (var i = itemsToProcess.length; i > 0; i--) {
    chain = chain.then(getDeferredResult);
}

...しかし、そのようにitemsToProcessをループするのは面倒です。または、再帰を抽象化する「ループ」と呼ばれる新しい関数を定義します。より良い方法は何ですか?

32
Cheeso

_[].reduce_ を使用してこれを行うには、すてきなクリーンな方法があります。

_var chain = itemsToProcess.reduce(function (previous, item) {
    return previous.then(function (previousValue) {
        // do what you want with previous value
        // return your async operation
        return Q.delay(100);
    })
}, Q.resolve(/* set the first "previousValue" here */));

chain.then(function (lastResult) {
    // ...
});
_

reduce 配列を反復処理し、前の反復の戻り値を渡します。この場合、promiseを返すので、毎回thenをチェーンします。最初の約束を(q.resolve("start")で行ったように)提供して、物事を開始します。

最初はここで何が起こっているのかを理解するのに少し時間がかかる場合がありますが、少し時間をかけてそれを処理すると、機械をセットアップする必要がなく、どこでも簡単に使用できるパターンになります。

75
Stuart K

以下は、Qで定義された状態マシンの概念です。

HTTP関数が定義されているとすると、Q promiseオブジェクトが返されます。

_var Q_http = function (url, options) {
  return Q.when($.ajax(url, options));
}
_

次のように再帰関数nextStateを定義できます。

_var states = [...]; // an array of states in the system.

// this is a state machine to control what url to get data from
// at the current state 
function nextState(current) {
  if (is_terminal_state(current))
    return Q(true);

  return Q_http(current.url, current.data).then(function (result) {
    var next = process(current, result);
    return nextState(next);
  });
}
_

ここで、function process(current, result)は、HTTP呼び出しからのcurrent状態およびresultに応じて次のステップがどうなるかを調べる関数です。

使用するときは、次のように使用します。

_nextState(initial).then(function () {
  // all requests are successful.
}, function (reason) {
  // for some unexpected reason the request sequence fails in the middle.
});
_
1
yuxhuang

私にはわかりやすい別の解決策を提案します。 promiseを直接チェーンする場合と同じようにします:promise.then(doSomethingFunction).then(doAnotherThingFunction);

これをループに入れると、次のようになります。

_var chain = Q.when();
for(...) {
  chain = chain.then(functionToCall.bind(this, arg1, arg2));
};
chain.then(function() {
    console.log("whole chain resolved");
});


var functionToCall = function(arg1, arg2, resultFromPreviousPromise) {
}
_

function currying を使用して、複数の引数を使用します。この例では、functionToCall.bind(this, arg1, arg2)は1つの引数を持つ関数を返します。functionToCall(resultFromPreviousPromise)前のpromiseの結果を使用する必要はありません。

1

私はこの方法が好きです:

_var q = require('q'),
    itemsToProcess =  ["one", "two", "three", "four", "five"];

function getDeferredResult(a) {
  return (function (items) {
    var deferred;

    // end
    if (items.length === 0) {
      return q.resolve(true);
    }

    deferred = q.defer();

    // any async function (setTimeout for now will do, $.ajax() later)
    setTimeout(function () {
      var a = items[0];
      console.log(a);
      // pop one item off the array of workitems
      deferred.resolve(items.splice(1));
    }, 600);

    return deferred.promise.then(getDeferredResult);
  }(a));
}

q.resolve(itemsToProcess)
  .then(getDeferredResult);
_

ここで重要なのは、_deferred.promise_の.then()を、ワークアイテムの配列のスプライスバージョンを使用して呼び出すことです。このthenは、最初の据え置き約束が解決された後に実行されます。これは、setTimeoutのfnにあります。より現実的なシナリオでは、延期されたプロミスはhttpクライアントのコールバックで解決されます。

最初のq.resolve(itemsToProcess)は、作業項目を作業fnの最初の呼び出しに渡すことで開始します。

他の人の役に立つことを願ってこれを追加しました。

1
Cheeso