如何等待一组异步回调函数?

IT技术 javascript asynchronous
2021-02-02 03:29:06

我的代码在 javascript 中看起来像这样:

forloop {
    //async call, returns an array to its callback
}

在所有这些异步调用完成后,我想计算所有数组的最小值。

我怎么能等他们呢?

我现在唯一的想法是有一个名为 done 的布尔数组,并在第 i 个回调函数中将 done[i] 设置为 true,然后说 while(并非全部都完成){}

编辑:我想一个可能但丑陋的解决方案是在每个回调中编辑完成的数组,然后调用一个方法,如果所有其他完成从每个回调中设置,因此最后一个完成的回调将调用继续方法。

提前致谢。

6个回答

你的代码不是很具体,所以我会编一个场景。假设您有 10 个 ajax 调用,并且您想累积这 10 个 ajax 调用的结果,然后当它们全部完成时,您想要做一些事情。您可以通过在数组中累积数据并跟踪最后一个完成的时间来做到这一点:

手动计数器

var ajaxCallsRemaining = 10;
var returnedData = [];

for (var i = 0; i < 10; i++) {
    doAjax(whatever, function(response) {
        // success handler from the ajax call

        // save response
        returnedData.push(response);

        // see if we're done with the last ajax call
        --ajaxCallsRemaining;
        if (ajaxCallsRemaining <= 0) {
            // all data is here now
            // look through the returnedData and do whatever processing 
            // you want on it right here
        }
    });
}

注意:错误处理在这里很重要(未显示,因为它特定于您如何进行 ajax 调用)。您将需要考虑如何处理当一个 ajax 调用永远不会完成时的情况,无论是出现错误还是卡住很长时间或很长时间后超时。


jQuery Promise

添加到我在 2014 年的回答中。如今,promise 通常用于解决此类问题,因为 jQuery$.ajax()已经返回一个 promise,并且$.when()会在一组 promise 全部解决时通知您,并为您收集返回结果:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push($.ajax(...));
}
$.when.apply($, promises).then(function() {
    // returned data is in arguments[0][0], arguments[1][0], ... arguments[9][0]
    // you can process it here
}, function() {
    // error occurred
});

ES6 标准Promise

正如 kba 的回答中所指定的:如果您有一个内置本机Promise的环境(现代浏览器或 node.js 或使用 babeljs transpile 或使用Promise polyfill),那么您可以使用 ES6 指定的Promise。请参阅此表以了解浏览器支持。几乎所有当前浏览器都支持 Promise,但 IE 除外。

如果doAjax()返回一个Promise,那么你可以这样做:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

如果您需要将非Promise异步操作变成一个返回Promise的异步操作,您可以像这样“Promise”它:

function doAjax(...) {
    return new Promise(function(resolve, reject) {
        someAsyncOperation(..., function(err, result) {
            if (err) return reject(err);
            resolve(result);
        });
    });
}

然后使用上面的模式:

var promises = [];
for (var i = 0; i < 10; i++) {
    promises.push(doAjax(...));
}
Promise.all(promises).then(function() {
    // returned data is in arguments[0], arguments[1], ... arguments[n]
    // you can process it here
}, function(err) {
    // error occurred
});

蓝鸟Promise

如果您使用功能更丰富的库,例如Bluebird Promise库,那么它会内置一些附加功能来简化此操作:

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });
@philx_x - 这么认为。现在你需要一个 polyfill 库才能使用 fetch。从你关于避免使用 ajax 库的评论中取出一点空气。Fetch 很好,但要在没有 polyfill 的情况下使用它还有几年的时间。它甚至还没有出现在所有浏览器的最新版本中。做,它并没有真正改变我的答案。我有一个doAjax()返回Promise作为选项之一。一样的东西fetch()
2021-03-15 03:29:06
这些天有一个更干净的解决方案,它甚至不需要 jquery。我正在使用 FetchAPI 和 Promises
2021-03-20 03:29:06
@philx_x - 你对 IE 和 Safari 支持做了什么?
2021-03-23 03:29:06
@kba - 我不会确切地称这个答案已经过时,因为所有技术仍然适用,特别是如果您已经将 jQuery 用于 Ajax。但是,我已经通过多种方式对其进行了更新以包含本机Promise。
2021-04-04 03:29:06
@jfriend00 github 做了一个polyfill github.com/github/fetch或者我不确定 babel 是否支持 fetch。babeljs.io
2021-04-04 03:29:06

从 2015 年开始签入:我们现在最新的浏览器(Edge 12、Firefox 40、Chrome 43、Safari 8、Opera 32 和 Android 浏览器 4.4.4 和 iOS Safari 8.4,但不包括 Internet Explorer、Opera Mini 和旧版本)中提供本机Promise安卓)。

如果我们想执行 10 个异步操作并在它们全部完成时得到通知,我们可以使用 native Promise.all,而无需任何外部库:

function asyncAction(i) {
    return new Promise(function(resolve, reject) {
        var result = calculateResult();
        if (result.hasError()) {
            return reject(result.error);
        }
        return resolve(result);
    });
}

var promises = [];
for (var i=0; i < 10; i++) {
    promises.push(asyncAction(i));
}

Promise.all(promises).then(function AcceptHandler(results) {
    handleResults(results),
}, function ErrorHandler(error) {
    handleError(error);
});
您的回答还需要参考您可以使用哪些浏览器Promise.all(),其中不包括当前版本的 IE。
2021-03-18 03:29:06
Promises.all()应该是Promise.all()
2021-04-07 03:29:06

您可以将 jQuery 的Deferred对象与when方法一起使用

deferredArray = [];
forloop {
    deferred = new $.Deferred();
    ajaxCall(function() {
      deferred.resolve();
    }
    deferredArray.push(deferred);
}

$.when(deferredArray, function() {
  //this code is called after all the ajax calls are done
});
@jfriend00 当它已经在 jQuery 中创建时,我不想重新发明轮子
2021-03-12 03:29:06
@Paul 所以宁愿重新发明轮子,包括 40kb 的垃圾来做一些简单的事情(延期)
2021-03-13 03:29:06
但并不是每个人都可以或想要使用 jQuery,SO 上的习惯是通过是否用 jQuery 标记您的问题来表明这一点。
2021-03-19 03:29:06
该问题没有标记,jQuery这通常意味着 OP 不想要 jQuery 答案。
2021-04-06 03:29:06
$.when 调用是这个例子是不正确的。要等待延迟/Promise数组,您需要使用 $.when.apply($, promises).then(function() { /* do stuff */ })。
2021-04-06 03:29:06

你可以像这样模拟它:

  countDownLatch = {
     count: 0,
     check: function() {
         this.count--;
         if (this.count == 0) this.calculate();
     },
     calculate: function() {...}
  };

然后每个异步调用执行以下操作:

countDownLatch.count++;

在方法末尾的每个异步回调中,您添加以下行:

countDownLatch.check();

换句话说,您模拟了倒计时锁存功能。

在 99% 的用例中,Promise 是可行的方法,但我喜欢这个答案,因为它说明了在 Promise polyfill 比使用它的 JS 大的情况下管理异步代码的方法!
2021-04-10 03:29:06

这是我认为最简洁的方式。

Promise.all

获取API

(出于某种原因, Array.map 在 .then 函数中对我不起作用。但您可以使用 .forEach 和 [].concat() 或类似的东西)

Promise.all([
  fetch('/user/4'),
  fetch('/user/5'),
  fetch('/user/6'),
  fetch('/user/7'),
  fetch('/user/8')
]).then(responses => {
  return responses.map(response => {response.json()})
}).then((values) => {
  console.log(values);
})
我认为这需要return responses.map(response => { return response.json(); }), 或return responses.map(response => response.json())
2021-03-12 03:29:06