【问题标题】:How to execute promises in series?如何串行执行承诺?
【发布时间】:2017-05-24 16:42:29
【问题描述】:
var promiseReturningFuncs = [];
for(var i = 0; i < 5; i++){
  promiseReturningFuncs.push(askQuestion);
}

var programmers = [];
Promise.reduce(promiseReturningFuncs, function(resp, x) {
  console.log(typeof resp);
  if(typeof resp != "function") {
    programmers.push(resp);
  }
  return x();
})
.then(function(resp) {
  programmers.push(resp);
  console.log(programmers);
});

我的目标:依次执行 askQuestion 函数并解析由该函数创建的对象数组。 (此函数必须串行执行,以便响应用户输入)

想象一下,askQuestion 函数返回一个 promise,它解析了我想添加到数组中的对象。

这是我乱七八糟的做法。 我正在寻找一种更清洁的方法,理想情况下,我什至不需要推送到数组,我只需要一个最终的 .then,其中响应是一个数组。

【问题讨论】:

  • 您可能会将其视为throttling problem,其中在给定时间打开的 Promise 数量被限制为 1。
  • Promise.all(promiseReturningFuncs.map(func=>func())).then(console.log);
  • 你尝试过使用 promise.all() 吗?
  • 如果这是您想要改进的工作代码,那么这个问题可能属于codereview.stackexchange.com。如果这不是工作代码,请准确描述它产生的输出以及您希望它产生的输出。
  • 您可以在此处查看一些用于排序异步操作的设计模式:How to synchronize a sequence of promises。此外,如果您使用的是 Bluebird Promise 库(看起来可能是这样),那么 Promise.mapSeries() 可能就是您想要的。

标签: javascript asynchronous promise


【解决方案1】:

由于您似乎在使用 Bluebird Promise 库,因此您有许多内置选项可用于对您的 Promise 返回函数进行排序。您可以使用并发值为 1 的 Promise.reduce()Promise.map()Promise.mapSeriesPromise.each()。如果迭代器函数返回一个 Promise,所有这些都将等待下一次迭代,直到该 Promise 解决。使用哪个更多地取决于数据结构的机制以及您想要的结果(您实际展示或描述的结果都没有)。

假设您有一组 Promise 返回函数,并且您想一次调用一个,等待一个解决后再调用下一个。如果你想要所有的结果,那么我建议Promise.mapSeries():

let arrayOfPromiseReturningFunctions = [...];

// call all the promise returning functions in the array, one at a time
// wait for one to resolve before calling the next
Promise.mapSeries(arrayOfPromiseReturningFunctions, function(fn) {
    return fn();
}).then(function(results) {
     // results is an array of resolved results from all the promises
}).catch(function(err) {
     // process error here
});

Promise.reduce() 也可以使用,但它会累积一个结果,将它从一个传递到下一个并以一个最终结果结束(就像Array.prototype.reduce() 一样)。

Promise.map()Promise.mapSeries() 的更通用版本,可让您控制并发数(同时进行中的异步操作数)。

Promise.each() 也会对你的函数进行排序,但不会累积结果。它假设您没有结果,或者您正在带外或通过副作用累积结果。我倾向于不喜欢使用Promise.each(),因为我不喜欢副作用编程。

【讨论】:

    【解决方案2】:

    您可以使用 ES6 (ES2015) 功能在纯 JS 中解决此问题:

    function processArray(arr, fn) {
        return arr.reduce(
            (p, v) => p.then((a) => fn(v).then(r => a.concat([r]))),
            Promise.resolve([])
        );
    }
    

    它将给定的函数串联应用于数组并解析为结果数组

    用法:

    const numbers = [0, 4, 20, 100];
    const multiplyBy3 = (x) => new Promise(res => res(x * 3));
    
    // Prints [ 0, 12, 60, 300 ]
    processArray(numbers, multiplyBy3).then(console.log);
    

    您需要仔细检查浏览器兼容性,但这适用于当前相当流行的 Chrome (v59)、NodeJS (v8.1.2) 以及可能的大多数其他浏览器。

    【讨论】:

      【解决方案3】:

      您可以使用递归,以便在 then 块中移动到下一个迭代。

      function promiseToExecuteAllInOrder(promiseReturningFunctions /* array of functions */) {
        var resolvedValues = [];
      
        return new Promise(function(resolve, reject) {
          function executeNextFunction() {
            var nextFunction = promiseReturningFunctions.pop();
            if(nextFunction) {
              nextFunction().then(function(result) {
                resolvedValues.push(result);
                executeNextFunction();
              });
            } else {
              resolve(resolvedValues);
            }
          }
          executeNextFunction();
        }
      }
      

      【讨论】:

      【解决方案4】:

      使用递归函数(以非承诺方式)一个接一个地执行:

      (function iterate(i,result,callback){
       if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
      })(0,[],console.log);
      

      为了保证这可以包含在一个承诺中:

      function askFive(){
      return new Promise(function(callback){
      (function iterate(i,result){
       if( i>5 ) callback(result);askQuestion().then(res=>iterate(i+1,result.concat([res]),callback);
      })(0,[],console.log);
      });
      }
      
      askFive().then(console.log);
      

      或者:

      function afteranother(i,promise){
         return new Promise(function(resolve){
           if(!i) return resolve([]);
           afteranother(i-1,promise).then(val=>promise().then(val2=>resolve(val.concat([val2])));
         });
      }
      
      afteranother(5,askQuestion).then(console.log);
      

      【讨论】:

      • 这将并行执行所有问题,而不是串行执行。我认为这不是 OP 所要求的。
      • 这是并行的,我需要按照我的问题中的说明依次完成。
      • 为什么回退到回调?回报你的承诺!
      • 抱歉,我试图避免递归,这是我最初的解决方案,但是是的,这肯定会工作,谢谢!
      • @Jonasw 你不需要(也不应该)wrap it in a new Promise
      猜你喜欢
      • 1970-01-01
      • 2018-09-03
      • 2017-07-01
      • 2020-07-01
      • 2021-05-14
      • 2013-02-24
      • 2023-03-19
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多