How can I wait for set of asynchronous callback functions?
Asked Answered
F

8

105

I have code that looks something like this in javascript:

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

After ALL of those async calls are done, I want to calculate the min over all of the arrays.

How can I wait for all of them?

My only idea right now is to have an array of booleans called done, and set done[i] to true in the ith callback function, then say while(not all are done) {}

edit: I suppose one possible, but ugly solution, would be to edit the done array in each callback, then call a method if all other done are set from each callback, thus the last callback to complete will call the continuing method.

Fevre answered 4/4, 2012 at 2:14 Comment(4)
On async do you mean waiting for an Ajax request to complete?Jugulate
Note, while (not all are done) { } wouldn't work. While you're busy-waiting, none of your callbacks can run.Enosis
Yes. I'm waiting an async call to an external API to return so that it will fire the callback methods. Yeah cHao, I realized that, which is why I'm asking for help here :DFevre
You could try this: github.com/caolan/async Very nice set of async utility functions.Upside
S
204

You haven't been very specific with your code, so I'll make up a scenario. Let's say you have 10 ajax calls and you want to accumulate the results from those 10 ajax calls and then when they have all completed you want to do something. You can do it like this by accumulating the data in an array and keeping track of when the last one has finished:

Manual Counter

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

Note: error handling is important here (not shown because it's specific to how you're making your ajax calls). You will want to think about how you're going to handle the case when one ajax call never completes, either with an error or gets stuck for a long time or times out after a long time.


jQuery Promises

Adding to my answer in 2014. These days, promises are often used to solve this type of problem since jQuery's $.ajax() already returns a promise and $.when() will let you know when a group of promises are all resolved and will collect the return results for you:

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 Standard Promises

As specified in kba's answer: if you have an environment with native promises built-in (modern browser or node.js or using babeljs transpile or using a promise polyfill), then you can use ES6-specified promises. See this table for browser support. Promises are supported in pretty much all current browsers, except IE.

If doAjax() returns a promise, then you can do this:

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

If you need to make a non-promise async operation into one that returns a promise, you can "promisify" it like this:

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

And, then use the pattern above:

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

Bluebird Promises

If you use a more feature rich library such as the Bluebird promise library, then it has some additional functions built in to make this easier:

 var doAjax = Promise.promisify(someAsync);
 var someData = [...]
 Promise.map(someData, doAjax).then(function(results) {
     // all ajax results here
 }, function(err) {
     // some error here
 });
Smolensk answered 4/4, 2012 at 2:19 Comment(5)
@kba - I wouldn't have exactly called this answer outdated since all the techniques are still applicable, particularly if you're already using jQuery for Ajax. But, I've updated it in several ways to include native promises.Smolensk
theese days there is a much cleaner solution which doesnt even need jquery. I'm doing it with FetchAPI and PromisesMessage
@Message - What are you doing about IE and Safari support?Smolensk
@Smolensk github made a polyfill github.com/github/fetch. Or i'm not sure if babel supports fetch yet. babeljs.ioMessage
@Smolensk yeah .. my answer was probably a little hasty, if you don't develop for production though Promise together with fetch() is soooo nice :)Message
L
18

Checking in from 2015: We now have native promises in most recent browser (Edge 12, Firefox 40, Chrome 43, Safari 8, Opera 32 and Android browser 4.4.4 and iOS Safari 8.4, but not Internet Explorer, Opera Mini and older versions of Android).

If we want to perform 10 async actions and get notified when they've all finished, we can use the native Promise.all, without any external libraries:

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);
});
Landed answered 19/11, 2015 at 20:29 Comment(2)
Promises.all() should be Promise.all().Smolensk
Your answer also needs to refer to which browsers you can use Promise.all() in which includes no current versions of IE.Smolensk
C
10

You can use jQuery's Deferred object along with the when method.

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
});
Consensual answered 4/4, 2012 at 2:19 Comment(6)
The question wasn't tagged for jQuery which usually means the OP did not want a jQuery answer.Smolensk
@Smolensk I didn't want to reinvent the wheel when it was already created in jQueryConsensual
@Consensual so rather then re-invent the wheel your including 40kb of junk to do something simple (deferreds)Miosis
But not everybody can or wants to use jQuery and the custom here on SO is that you indicate that by whether you tag your question with jQuery or not.Smolensk
@Smolensk thanks for letting me know about that. I'll be sure to check the tags in the future.Consensual
The $.when call is this example is incorrect. To wait for an array of deferred/promises you need to use $.when.apply($, promises).then(function() { /* do stuff */ }).Kathleenkathlene
T
9

You can emulate it like this:

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

then each async call does this:

countDownLatch.count++;

while in each asynch call back at the end of the method you add this line:

countDownLatch.check();

In other words, you emulate a count-down-latch functionality.

Tahmosh answered 4/4, 2012 at 2:21 Comment(1)
In 99% of all the use cases a Promise is the way to go but I like this answer because it illustrates a method to manage Async code in situations where a Promise polyfill is larger then the JS that uses it!Tb
M
6

This is the most neat way in my opinion.

Promise.all

FetchAPI

(for some reason Array.map doesn't work inside .then functions for me. But you can use a .forEach and [].concat() or something similar)

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);
})
Message answered 14/4, 2016 at 19:30 Comment(1)
I think this needs to be return responses.map(response => { return response.json(); }), or return responses.map(response => response.json()).Profusive
M
1

Use an control flow library like after

after.map(array, function (value, done) {
    // do something async
    setTimeout(function () {
        // do something with the value
        done(null, value * 2)
    }, 10)
}, function (err, mappedArray) {
    // all done, continue here
    console.log(mappedArray)
})
Miosis answered 4/4, 2012 at 2:28 Comment(0)
F
1

I see several response with Promise.all(), but this function stop if any promise generate an exception...

The best solution in 2022 is Promise.allSettled() (documentation here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled

A quick sample:

const allPromises = [];
for (r in records) {
  const promise = update_async(r);
  allPromises.push(promise);
};
await Promise.allSettled(allPromises);

At the end, you obtain with allPromises an array with the result of each promise:

  • when ok --> {status: “fulfilled”, value: xxx }
  • when error --> {status: "rejected", reason: Error: an error}
Falter answered 22/12, 2022 at 14:27 Comment(0)
T
0

In Node.js you can use async/await to control the async flow

  • async/await is supported in Node.js 7.6
  • util function to promisify callback is supported in Node.js v8

Sample Code:

const foo = async () => {
  try {
    const ids = [100, 101, 102];
    const fetchFromExternalApi = util.promisify(fetchFromExternalApiCallback);
    const promises = ids.map((id) => fetchFromExternalApi(id));
    const dataList = await Promise.resolve(promises); // dataList is an array
    return dataList;
  } catch (err) {
    // error handling
  }
};
Tramel answered 11/8, 2021 at 4:44 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.