如何在Q中按顺序链接可变数量的promise?

时间:2013-07-20 01:09:50

标签: node.js promise q

  

我见过Chaining an arbitrary number of promises in Q;我的问题不同。

如何进行可变数量的调用,每个调用按顺序异步返回?
该场景是一组HTTP请求,其数量和类型由第一个HTTP请求的结果确定。

我想简单地这样做。

我也看到this answer建议这样的事情:

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似乎很尴尬。或者定义一个名为“loop”的新函数来抽象递归。什么是更好的方式?

4 个答案:

答案 0 :(得分:75)

使用[].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遍历数组,传入上一次迭代的返回值。在这种情况下,您将返回承诺,因此每次链接then时都是如此。你提供了一个初步的承诺(正如你对q.resolve("start")所做的那样)来启动。

起初可能需要一段时间来围绕这里发生的事情,但如果你花一点时间来完成它,那么这是一个易于使用的模式,无需设置任何机器。

答案 1 :(得分: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在初始延迟的promise解析之后运行,它在setTimeout的fn中。在更现实的情况下,延迟的承诺将在http客户端回调中得到解决。

最初的q.resolve(itemsToProcess)通过将工作项传递给工作fn的第一次调用来解决问题。

我补充说希望它可以帮助别人。

答案 2 :(得分:1)

以下是使用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)是一个函数,用于根据current状态和HTTP调用中的result找出下一步的内容。

使用时,请使用它:

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

答案 3 :(得分:1)

我提出了另一种解决方案,对我来说更容易理解。 您可以像直接链接承诺一样: 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)将返回一个带有一个参数的函数:functionToCall(resultFromPreviousPromise) 您不需要使用先前承诺的结果。