如何在循环中返回多个(并行)异步函数调用的累积结果?

时间:2016-07-17 23:05:38

标签: javascript

我有一个函数foo,它在循环中进行多个(并行)异步调用。我需要等到所有呼叫的结果都可用。如何从foo返回完整结果,或者在所有数据可用后触发某些处理?

我尝试将每个结果添加到数组中,但之后数组不会填充,直到我需要使用它之后。

function foo() {
    var results = [];

    for (var i = 0; i < 10; i++) {
      someAsyncFunction({someParam:i}, function callback(data) {
        results.push(data);
      });
    }
    return results;
}

var result = foo(); // It always ends up being an empty array at this point.

注意:这个问题是按照现有通用"How do I return the response from an asynchronous call?" question的方式故意通用的。这个问题有一些很好的答案,但并不涵盖多个异步调用。还有一些其他问题提到多个调用,但我找不到任何基于循环的调用,有些只有jQuery答案等等。我希望这里有一些不通用的技术。取决于特定的图书馆。

5 个答案:

答案 0 :(得分:11)

使用承诺。确切地说,Promise.all就是为此设计的。

它接受一个数组(或可迭代的)promises,并返回一个新的promise,当数组的所有promises都已解析时,它会被解析。否则,它拒绝任何数组的承诺拒绝。

&#13;
&#13;
function someAsyncFunction(data, resolve, reject) {
  setTimeout(function() {
    if(Math.random() < .05) {
      // Suppose something failed
      reject('Error while processing ' + data.someParam);
    } else {
      // Suppose the current async work completed succesfully
      resolve(data.someParam);
    }
  }, Math.random() * 1000);
}

function foo() {
  
  // Create an array of promises
  var promises = [];
  
  for (var i = 0; i < 10; i++) {
    // Fill the array with promises which initiate some async work
    promises.push(new Promise(function(resolve, reject) {
      someAsyncFunction({someParam:i}, resolve, reject);
    }));
  }
  
  // Return a Promise.all promise of the array
  return Promise.all(promises);
}

var result = foo().then(function(results) {
  console.log('All async calls completed successfully:');
  console.log(' --> ', JSON.stringify(results));
}, function(reason) {
  console.log('Some async call failed:');
  console.log(' --> ', reason);
});
&#13;
&#13;
&#13;

请注意,结果将根据承诺数组的顺序给出,而不是按照承诺被解决的顺序给出。

答案 1 :(得分:2)

一种简单的方法是在所有响应都在数组中时触发​​回调:

function foo(cb) {
    var results = [];

    for (var i = 0; i < 10; i++) {
      someAsyncFunction({someParam:i}, function callback(data) {
        results.push(data);

        if(results.length===10){
          cb(results);
        }
      });
    }

}

foo(function(resultArr){
    // do whatever with array of results
});

Promise.all方法的区别仅在于无法保证结果的顺序;但只需添加一些内容即可轻松实现。

答案 2 :(得分:2)

很久以前我在这里回答了一个非常相似的问题:Coordinating parallel execution in node.js

然而,时代已经过去了。从那时起,一个非常好的图书馆已经出现,并且承诺设计模式已经被充分探索,甚至标准化为语言。如果您想了解如何使用原始代码完成,请单击上面的链接。如果你只想编写阅读...

async.js

async.js library基本上已经实现了上面链接中的代码。使用async,您编写的代码将如下所示:

var listOfAsyncFunctions = [];

for (var i = 0; i < 10; i++) {
    (function(n){
        // Construct an array of async functions with the expected
        // function signature (one argument that is the callback).
        listOfAsyncFunctions.push(function(callback){
            // Note: async expects the first argument to callback to be an error
            someAsyncFunction({someParam:n}, function (data) {
                callback(null,data);
            });
        })
    })(i); // IIFE to break the closure
}

// Note that at this point you haven't called the async functions.
// Pass the array to async.js and let it call them.

async.parallel(listOfAsyncFunctions,function (err,result) {
    console.log(result); // result will be the same order as listOfAsyncFunctions
});

然而,async.js的作者已经做了更多。 Async还具有类似功能的数组操作:each,map,filter,reduce。它使得异步处理数组变得简单,并使代码更容易理解:

var listOfParams = [];

for (var i = 0; i < 10; i++) {
    // Construct an array of params:
    listOfParams.push({someParam:i});
}

async.map(listOfParams,someAsyncFunction,function (err,result) {
    console.log(result);
});

async给你的另一个东西是如何处理异步任务的不同算法。比如说你想抓一个网站,但不希望他们禁止你的IP地址垃圾邮件他们的服务器。您可以使用async.series()代替parallel来一次处理一项任务:

// Set-up listOfAsyncFunctions as above

async.series(listOfAsyncFunctions,function (err,result) {
    console.log(result); // result will be the same order as listOfAsyncFunctions
});

或者,如果您想一次处理3个任务:

async. parallelLimit(listOfAsyncFunctions, 3, function (err,result) {
    console.log(result); // result will be the same order as listOfAsyncFunctions
});

Promise.all()

Promise.all()方法的工作方式与async.parallel()类似,只是它适用于promises。您构造一个promises数组,然后将它们传递给Promise.all()

var listOfPromises = [];

for (var i = 0; i < 10; i++) {
    // Construct an array of promises
    listOfPromises.push(somePromiseFunction({someParam:i}));
}

Promise.all(listOfPromises).then(function(result){
    console.log(result);
});

答案 3 :(得分:0)

不要使用Promise.all!如果您的任何一项承诺都失败了,那将使整个操作失败!

除非您对此前景感到满意,否则最好这样做:

function sleep(ms) {
  return new Promise((resolve, reject) => {
    console.log(`starting ${ms}`);
    setTimeout(() => {
      if (ms > 1000) {
        console.log(`Threw out ${ms} because it took too long!`);
        reject(ms);
      } else {
        console.log(`done ${ms}`);
        resolve(ms);
      }
    }, ms);
  });
}

(async () => {
  console.log('aPromise, bPromise, cPromise executed concurrently as promises are in an array');
  const start = new Date();
  const aPromise = sleep(2000);
  const bPromise = sleep(500);
  const cPromise = sleep(5);
  
  try {
    const [a, b, c] = [await aPromise, await bPromise, await cPromise];
    // The code below this line will only run when all 3 promises are fulfilled:
    console.log(`slept well - got ${a} ${b} ${c} in ${new Date()-start}ms`);
  } catch (err) {
    console.log(`slept rough in ${err}ms`);
  }
})();

答案 4 :(得分:0)

正如其他答案提到的那样,承诺是一个很好的选择。 Promise.all()一并提到,但如果其中一项承诺失败,则返回并拒绝immediately

如果您希望

Promise.allSettled()仅在所有诺言完成后才返回,则是一个不错的选择。这样可以处理是否兑现了一些承诺而其他承诺被拒绝的情况。

以下是Mozilla文档中的示例:

Promise.allSettled([
  Promise.resolve(33),
  new Promise(resolve => setTimeout(() => resolve(66), 0)),
  99,
  Promise.reject(new Error('an error'))
])
.then(values => console.log(values));

// [
//   {status: "fulfilled", value: 33},
//   {status: "fulfilled", value: 66},
//   {status: "fulfilled", value: 99},
//   {status: "rejected",  reason: Error: an error}
// ]