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

IT技术 javascript
2021-03-05 11:39:24

我有一个函数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.

注意:这个问题是故意按照现有的泛型“如何从异步调用返回响应?”的泛型问题这个问题有一些很好的答案,但不包括多个异步调用。还有一些其他问题提到了多次调用,但我找不到任何基于循环的问题,有些只有 jQuery 答案,等等。我希望这里有一些不依赖于特定库的通用技术。

5个回答

使用Promise。正是Promise.all为此而设计的。

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

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

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

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

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方法的唯一区别是不保证结果的顺序;但这很容易通过添加一些内容来实现。

可以稍微小心地保证结果的顺序。请参阅:stackoverflow.com/questions/4631774/...
2021-04-23 11:39:24

以前我在这里回答了一个非常类似的问题很长一段时间:协调在node.js的并行执行

然而,时代在进步。从那时起,一个非常好的库出现了,promise 设计模式得到了充分的探索,甚至标准化到了语言中。如果您想了解如何使用原始代码完成此操作,请单击上面的链接。如果你只是想代码阅读..

异步.js

async.js图书馆已基本实现的代码在上面的链接。使用异步,您编写的代码将如下所示:

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()仅适用于 Promise 的方式类似您构建了一个Promise数组,然后将它们传递给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);
});

不要使用 Promise.all!如果您的任何一项Promise失败,那么整个操作就会失败!

除非你对这个前景感到满意,否则你最好做这样的事情:

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`);
  }
})();

正如其他答案所提到的,Promises 是一个很好的方法。Promise.all()在一中提到过,但immediately如果其中一个Promise失败,则返回并拒绝

Promise.allSettled()是一个不错的选择,如果您希望它仅在所有Promise完成时返回。这允许处理某些Promise被履行而其他Promise被拒绝的情况。

这是来自 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}
// ]