Reputation: 4309
Suppose I have four distinct asynchronous operations that need to be run, and they can all be run independently. But there's one remaining function that needs to use all the data those asynchronous calls collect, so it can only be done once all of them are finished.
A simple way to do this is to make the asynchronous calls call each other one right after the other, and then finally call the final function, like so:
myObj.async1(function () {
myObj.async2(function () {
myObj.async3(function () {
myObj.async4(function () {
...
finalFunction();
But this is a poor way to do it, since node is built around asynchronous functionality for a reason. So instead, let's say we want to do:
myObj.async1(async1Callback);
myObj.async2(async2Callback);
myObj.async3(async3Callback);
myObj.async4(async4Callback);
if( //Some logic here to determine when all four functions have completed
finalFunction();
What's the best way to determine that logic? I considered having each function set a boolean variable to indicate whether it has completed, and then having a time-based emitter that constantly checks if all four variables are set to true and then calls finalFunction if they are, but that can get messy with having all those variables lying around.
Any thoughts on what's the best way to do this?
Upvotes: 1
Views: 220
Reputation: 12441
As @jabclab recommended, take a look at async as it manages much of the complexity for you. However, if you want to do something like this yourself here are a couple of alternatives.
Starting with a myObj that looks like:
var myObj = {
async1: function async1(cb) { setTimeout(function() {
console.log('async1');
cb(null, {name: 'async1'});
}, 1000)},
async2: function async2(cb) { setTimeout(function() {
console.log('async2');
cb(null, {name: 'async2'});
}, 500)},
async3: function async3(cb) { setTimeout(function() {
console.log('async3');
cb(null, {name: 'async3'});
}, 1001)},
async4: function async4(cb) { setTimeout(function() {
console.log('async4');
cb(null, {name: 'async4'});
}, 200)}
}
This version is hardcoded to call four specific functions and callback when the results are complete. The results passed back in an array ordered by completion. Each result object contains the name of the function as well as any error or success result.
function doFourSpecificThings(callback) {
var results = [];
var storeResults = function(fnName, err, resp) {
results.push( { fnName: fnName, err: err, resp: resp } );
if(results.length === 4 && callback) {
callback(results);
}
}
// Bind the callback to myObj and pass the name of the called function
// as the first argument
myObj.async1(storeResults.bind(myObj, 'async1'));
myObj.async2(storeResults.bind(myObj, 'async2'));
myObj.async3(storeResults.bind(myObj, 'async3'));
myObj.async4(storeResults.bind(myObj, 'async4'));
}
doFourSpecificThings(function(results) {
console.log(results);
});
Output:
async4
async2
async1
async3
Results:
[ { fnName: 'async4', err: null, resp: { name: 'async4' } },
{ fnName: 'async2', err: null, resp: { name: 'async2' } },
{ fnName: 'async1', err: null, resp: { name: 'async1' } },
{ fnName: 'async3', err: null, resp: { name: 'async3' } } ]
This version is a bit more flexible. The tasks are passed in as an array with the results being stored in the same order in the resulting array:
function doABunchOfStuff(tasks, callback) {
var results = [];
var expected = tasks.length;
var storeResults = function(idx, err, resp) {
results[idx] = { err: err, resp: resp };
--expected;
if((expected === 0) && callback) {
callback(results);
}
}
// Using bind here to pass the current index to the storeResults()
// callback as the first parameter
for(var i = 0; i < tasks.length; ++i) {
tasks[i](storeResults.bind(tasks[i], i));
}
}
doABunchOfStuff([
myObj.async1.bind(myObj),
myObj.async2.bind(myObj),
myObj.async3.bind(myObj),
myObj.async4.bind(myObj)],
function(results) {
console.log('\nResults:');
console.log(results);
});
Output:
async4
async2
async1
async3
Results:
[ { err: null, resp: { name: 'async1' } },
{ err: null, resp: { name: 'async2' } },
{ err: null, resp: { name: 'async3' } },
{ err: null, resp: { name: 'async4' } } ]
Upvotes: 2
Reputation: 15042
I would make use of the async
library for this, e.g.
async.parallel([
myObj.async1,
myObj.async2,
myObj.async3,
myObj.async4
], function(err) {
if (err) throw err;
// Run final function now that all prerequisites are finished
finalFunction();
});
This assumes that each myObj.async*
function
takes a callback function
as its only parameter and that callback's first parameter is an err
param. For more info see the docs for async#parallel()
.
Upvotes: 4