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 中。在更现实的情况下,延迟的承诺将在 http 客户端回调中得到解决。

初始q.resolve(itemsToProcess)通过将工作项传递给工作 fn 的第一个调用来启动。

我添加了这个,希望它可以帮助其他人。

于 2013-07-20T01:18:25.110 回答
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 回答
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)中,将返回一个带有一个参数的函数:functionToCall(resultFromPreviousPromise) 您不需要使用前一个 Promise 的结果。

于 2015-07-03T14:49:38.007 回答