In JavaScript parallel execution of asynchronous operations is the default. However, managing that with callbacks means that execution branches for each new thing. Collecting the results from those asynchronous operations back together, in order, without blocking each other is one of the JavaScript common pitfalls.
var resArray = [] //broken ;['foo','bar','baz'].forEach(function (name, i) { LOUD(name, function done (err, results) { if (err) throw err resArray[i] = results }) }) console.log('Yay!', resArray) // boo, empty array :( // LOUD hasn't done its work when this gets logged function LOUD(name, cb) { setTimeout(function () { cb(null, name.toUpperCase()) }, Math.random() * 1000) }
The easiest way to solve this problem is to have a function you call when they are all done. Simply by keeping count of all of the things you're expecting, and the things that come back you can know when they've all completed. This trusts that each of the callbacks you send out get called exactly once (like they should in a node style callback api). There are a lot of libraries that can do this, but I recommend writing it by hand at first to learn how they work.
var once = require('once') var cb = once(function (err, res) { if (err) return console.error(err) console.log('Yay!', res) }) var resArray = [] var total = 0 var called = 0 ;['foo','bar','baz'].forEach(function (name, i) { total += 1 LOUD(name, function done (err, results) { if (err) return cb(err) resArray[i] = results called += 1 if (total === called) cb(null, resArray) }) }) function LOUD(name, cb) { setTimeout(function () { cb(null, name.toUpperCase()) }, Math.random() * 1000) }
Once you're comfortable with that idea, you'll notice that while it's not hard, it is tedious. There is a lot of housekeeping that is is easy to screw up, so try abstracting it away into it's own library. We'll call it 'afterParallel'.
var once = require('once') var makeCb = afterParallel(function (err, res) { if (err) return console.error(err) console.log('Yay!', res) }) ;['foo','bar','baz'].forEach(function (name) { LOUD(name, makeCb()) }) function LOUD(name, cb) { setTimeout(function () { cb(null, name.toUpperCase()) }, Math.random() * 1000) } function afterParallel (fn) { var cb = once(fn) var resArray = [] var total = 0 var called = 0 return function makeDone () { if (called) { throw new Error('Added another parallel cb \ after results started comming back') } var position = total total += 1 return function done (err, results) { if (err) return cb(err) resArray[position] = results called += 1 if (total === called) cb(null, resArray) } } }
This isn't the only api possible, it's just my favorite. There are a lot of other good ways to think about this. Check out the async module's parallel, and the after-all node module. Whatever you use in production, playing with your own implementation will help you use other modules with more confidence.