【问题标题】:How to chain a variable number of promises in Q, in order?如何在 Q 中按顺序链接可变数量的 Promise?
【发布时间】:2013-07-19 10:07:23
【问题描述】:

我见过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 似乎很尴尬。或者定义一个名为“循环”的新函数来抽象递归。有什么更好的方法?

【问题讨论】:

  • 这似乎不是一个可变数量的promise,而是一个取决于数组长度的固定数量。在某些情况下,您实际上有一个可变数字,但我不确定 reduce 方法是否适用于这些情况。

标签: node.js promise q


【解决方案1】:

[].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") 所做的那样)来启动事情。

起初可能需要一段时间才能理解这里发生的事情,但如果你花一点时间来完成它,那么它是一种在任何地方都可以使用的简单模式,无需设置任何机器。

【讨论】:

  • 甜蜜!而且非常方便!
  • 如果 promise 被拒绝会发生什么?或者,至少,我们将如何处理这种情况?
  • @naivedeveloper 如果一个步骤被拒绝,那么下面的步骤将不会被执行,chain 也将被拒绝,所以你可以用chain.catch 来捕捉这种情况。
  • 非常好的解决方案。谢谢!
  • 这是完美的,在我看到它运行之前融化了我的大脑。然后它就很有意义了。
【解决方案2】:

我更喜欢这种方式:

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 的第一个调用来启动。

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

【讨论】:

  • 按照您的第一个示例在循环中构建.then() 链可能比递归更正常。无论您选择哪种方式,不要破坏原始数组可能很重要。如果是这样,那么您可以让 Promise(及其回调)使用对原始 unsullied 数组进行操作的递增索引值。
【解决方案3】:

这是一个用Q定义的状态机的概念。

假设您定义了 HTTP 函数,因此它返回一个 Q 承诺对象:

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

【讨论】:

  • 这对我来说比其他的 while-loop 解决方案更有意义。也更容易适应其他 Promise 库。
【解决方案4】:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-06-03
    • 2013-09-26
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    • 2023-03-22
    • 2013-08-25
    相关资源
    最近更新 更多