我怎么能等待一组异步回调函数?

cod*_*ple 92 javascript asynchronous

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

forloop {
    //async call, returns an array to its callback
}
Run Code Online (Sandbox Code Playgroud)

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

我怎么能等他们全部?

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

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

提前致谢.

jfr*_*d00 184

你的代码并不是很具体,所以我会编写一个场景.假设您有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
        }
    });
}
Run Code Online (Sandbox Code Playgroud)

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


jQuery承诺

在2014年添加我的答案.现在,承诺通常用于解决此类问题,因为jQuery $.ajax()已经返回一个承诺,并$.when()会告诉您何时一组承诺都已解决并将为您收集返回结果:

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
});
Run Code Online (Sandbox Code Playgroud)

ES6标准承诺

正如kba的答案所述:如果你有一个内置本机promises的环境(现代浏览器或node.js或使用babeljs transile或使用promise polyfill),那么你可以使用ES6指定的promises.有关浏览器支持,请参阅此表.几乎所有当前浏览器都支持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
});
Run Code Online (Sandbox Code Playgroud)

如果你需要对一个返回promise的非同步异步操作,你可以像这样"promisify"它:

function doAjax(...) {
    return new Promise(function(resolve, reject) {
        someAsyncOperation(..., function(err, result) {
            if (err) return reject(err);
            resolve(result);
        });
    });
}
Run Code Online (Sandbox Code Playgroud)

然后,使用上面的模式:

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
});
Run Code Online (Sandbox Code Playgroud)

蓝鸟承诺

如果您使用功能更丰富的库(如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
 });
Run Code Online (Sandbox Code Playgroud)

  • @kba - 我不会完全称这个答案过时,因为所有技术仍然适用,特别是如果你已经在使用jQuery for Ajax.但是,我已经通过几种方式更新它以包含本机承诺. (4认同)

kba*_*kba 14

从2015年开始登记:我们现在在最新的浏览器(Edge 12,Firefox 40,Chrome 43,Safari 8,Opera 32和Android浏览器4.4.4和iOS Safari 8.4,但不是Internet Explorer,Opera Mini和旧版本)中有本机承诺 Android).

如果我们想要执行10个异步操作并在完成后获得通知,我们可以使用本机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);
});
Run Code Online (Sandbox Code Playgroud)

  • `Promises.all()` 应该是 `Promise.all()`。 (2认同)

Pau*_*aul 10

您可以使用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
});
Run Code Online (Sandbox Code Playgroud)

  • @jfriend00我不想在jQuery中创建它时重新发明轮子 (8认同)
  • 问题没有被标记为`jQuery`,这通常意味着OP不想要jQuery答案. (7认同)
  • @Paul然后重新发明轮子你包括40kb的垃圾来做一些简单的事情(延期) (4认同)
  • $ .when调用此示例是不正确的.要等待一系列延迟/承诺,你需要使用$ .when.apply($,promises).then(function(){/*do stuff*/}). (4认同)
  • 但并不是每个人都可以或想要使用jQuery,而且这里的自定义是你通过jQuery标记你的问题来表明你的问题. (2认同)

Eug*_*sky 9

你可以像这样模仿它:

  countDownLatch = {
     count: 0,
     check: function() {
         this.count--;
         if (this.count == 0) this.calculate();
     },
     calculate: function() {...}
  };
Run Code Online (Sandbox Code Playgroud)

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

countDownLatch.count++;
Run Code Online (Sandbox Code Playgroud)

而在每个asynch回调的方法结束时,你添加这一行:

countDownLatch.check();
Run Code Online (Sandbox Code Playgroud)

换句话说,您可以模拟倒计时锁存功能.


phi*_*x_x 5

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

无极

提取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);
})
Run Code Online (Sandbox Code Playgroud)