14

What's the best way/library for handling multiple asynchronous callbacks? Right now, I have something like this:

_.each(stuff, function(thing){
   async(thing, callback);
});

I need to execute some code after the callback has been fired for each element in stuff.

What's the cleanest way to do this? I'm open to using libraries.

Stefan Kendall
  • 66,414
  • 68
  • 253
  • 406
  • possible duplicate of [Waiting on multiple asynchronous calls to complete before continuing](http://stackoverflow.com/questions/2768293/waiting-on-multiple-asynchronous-calls-to-complete-before-continuing) – RMalke Apr 26 '14 at 15:32

6 Answers6

28

Since you're already using Underscore you might look at _.after. It does exactly what you're asking for. From the docs:

after    _.after(count, function)

Creates a version of the function that will only be run after first being called count times. Useful for grouping asynchronous responses, where you want to be sure that all the async calls have finished, before proceeding.

Jordan Running
  • 102,619
  • 17
  • 182
  • 182
13

There is a great library called Async.js that helps solve problems like this with many async & flow control helpers. It provides several forEach functions that can help you run callbacks for every item in a an array/object.

Check out: https://github.com/caolan/async#forEach

// will print 1,2,3,4,5,6,7,all done

var arr = [1,2,3,4,5,6,7];

function doSomething(item, done) {
  setTimeout(function() {
    console.log(item);
    done(); // call this when you're done with whatever you're doing
  }, 50);
}

async.forEach(arr, doSomething, function(err) {
    console.log("all done");
});
Jamund Ferguson
  • 16,721
  • 3
  • 42
  • 50
  • This is good, and if this can handle chaining for me, I may make this switch later. Does this do something more intelligent than simply using a counter like _.after? – Stefan Kendall Apr 24 '12 at 03:14
  • The cool thing is you can do `async.forEachSerial` if you want them to run one after another. It gives you flexibility. For a simple use case, yeah the count thing works great and is built into underscore. Do it! – Jamund Ferguson Apr 24 '12 at 03:19
  • I wound up needing to use 'series' quite a bit, and switching to async cleaned things up *quite* a bit. I'm also pretty sure I discovered a callback timing issue in the process. – Stefan Kendall Apr 25 '12 at 20:05
  • @StefanKendall interesting! If I recall there can be problems if you return immediately, meaning your function isn't asynchronous. I believe that can be solved by wrapping early return in a setTimeout(fn, 0); ! – Jamund Ferguson Apr 25 '12 at 20:44
2

I recommend https://github.com/caolan/async for this. You can use async.parallel to do this.

function stuffDoer(thing) {
    return function (callback) {
        //Do stuff here with thing
        callback(null, thing);
    }
}

var work = _.map(stuff, stuffDoer)
async.parallel(work, function (error, results) {
    //error will be defined if anything passed an error to the callback
    //results will be an unordered array of whatever return value if any
    //the worker functions passed to the callback
}
Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • I think in this case async.forEach and async.parallel are basically identical. The docs on forEach even say "it executes them in parallel". The "parallel" name makes this clearer though. – Peter Lyons Apr 24 '12 at 12:50
  • Hmm, yeah it seems forEach is more appropriate in this case since it will handle the closures for you and you can just give it a regular function and a list of data. – Peter Lyons Apr 24 '12 at 19:13
1

async.parallel() / async.series should suit your requirement. You can provide with a final callback that gets executed when all the REST calls succeed.

async.parallel([
    function(){ ... },
    function(){ ... }
], callback);
async.series([
    function(){ ... },
    function(){ ... }
], callback);
skashyap
  • 143
  • 10
0

Have a counter, say async_count. Increase it by one every time you start a request (inside you loop) and have the callback reduce it by one and check if zero has been reached - if so, all the callbacks have returned.

EDIT: Although, if I were the one writing this, I would chain the requests rather than running them in parallel - in other words, I have a queue of requests and have the callback check the queue for the next request to make.

Niet the Dark Absol
  • 320,036
  • 81
  • 464
  • 592
0

See my response to a similar question:

Coordinating parallel execution in node.js

My fork() function maintains the counter internally and automatically.

Community
  • 1
  • 1
slebetman
  • 109,858
  • 19
  • 140
  • 171