33

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 をループするのは厄介なようです。または、再帰を抽象化する「ループ」と呼ばれる新しい関数を定義します。より良い方法は何ですか?

4

4 に答える 4

76

を使用してこれを行うには、きれいな方法があり[].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")物事を開始する最初の約束を ( で行ったように) 提供します。

最初は、ここで何が起こっているのか理解するのに時間がかかるかもしれませんが、少し時間を取って作業を進めると、機械をセットアップする必要なく、どこでも簡単に使用できるパターンになります。

于 2013-07-20T16:57:48.850 に答える
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);

ここで重要なのは、作業項目の配列の結合されたバージョンを使用して を.then()呼び出すことです。deferred.promiseこれthenは、setTimeout の fn にある、最初の延期された promise が解決された後に実行されます。より現実的なシナリオでは、延期された promise は http クライアント コールバックで解決されます。

イニシャルq.resolve(itemsToProcess)は、作業項目を作業 fn の最初の呼び出しに渡すことによって開始します。

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

于 2013-07-20T01:18:25.110 に答える
1

別の解決策を提案します。これは私には理解しやすいようです。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) {
}

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

于 2015-07-03T14:49:38.007 に答える
1

で定義されるステート マシンの概念を次に示しQます。

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

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);
  });
}

状態とHTTP 呼び出しからfunction process(current, result)、次のステップが何であるかを調べる関数はどこにありますか。currentresult

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

nextState(initial).then(function () {
  // all requests are successful.
}, function (reason) {
  // for some unexpected reason the request sequence fails in the middle.
});
于 2013-07-20T03:00:53.050 に答える