How do I return the accumulated results of multiple (parallel) asynchronous function calls in a loop?

Solution 1:

Use promises. Precisely, Promise.all was designed for this.

It takes an array (or iterable) of promises, and returns a new promise which is resolved when all the promises of the array have been resolved. Otherwise, it rejects when any promise of the array rejects.

function someAsyncFunction(data, resolve, reject) {
  setTimeout(function() {
    if(Math.random() < .05) {
      // Suppose something failed
      reject('Error while processing ' + data.someParam);
    } else {
      // Suppose the current async work completed succesfully
      resolve(data.someParam);
    }
  }, Math.random() * 1000);
}

function foo() {
  
  // Create an array of promises
  var promises = [];
  
  for (var i = 0; i < 10; i++) {
    // Fill the array with promises which initiate some async work
    promises.push(new Promise(function(resolve, reject) {
      someAsyncFunction({someParam:i}, resolve, reject);
    }));
  }
  
  // Return a Promise.all promise of the array
  return Promise.all(promises);
}

var result = foo().then(function(results) {
  console.log('All async calls completed successfully:');
  console.log(' --> ', JSON.stringify(results));
}, function(reason) {
  console.log('Some async call failed:');
  console.log(' --> ', reason);
});

Note that the results will be given according to the order of the array of promises, not in the order that the promises were resolved in.

Solution 2:

A simple way of doing it would be to trigger a callback once all responses are in the array:

function foo(cb) {
    var results = [];

    for (var i = 0; i < 10; i++) {
      someAsyncFunction({someParam:i}, function callback(data) {
        results.push(data);

        if(results.length===10){
          cb(results);
        }
      });
    }

}

foo(function(resultArr){
    // do whatever with array of results
});

Only difference from the Promise.all approach is the order of the results is not guaranteed; but that is easily achievable with a few additions.

Solution 3:

A long time ago I've answered a very similar question here: Coordinating parallel execution in node.js.

However, times have moved on. Since then a really good library have appeared and the promise design pattern have been fully explored and even standardized into the langauge. If you want to see how it can be done with raw code click the link above. If you just want to code read on..

async.js

The async.js library have basically implemented the code in the link above. With async the code you'd write would look something like this:

var listOfAsyncFunctions = [];

for (var i = 0; i < 10; i++) {
    (function(n){
        // Construct an array of async functions with the expected
        // function signature (one argument that is the callback).
        listOfAsyncFunctions.push(function(callback){
            // Note: async expects the first argument to callback to be an error
            someAsyncFunction({someParam:n}, function (data) {
                callback(null,data);
            });
        })
    })(i); // IIFE to break the closure
}

// Note that at this point you haven't called the async functions.
// Pass the array to async.js and let it call them.

async.parallel(listOfAsyncFunctions,function (err,result) {
    console.log(result); // result will be the same order as listOfAsyncFunctions
});

However, the authors of async.js have done more than that. Async also have functional array-like operations: each, map, filter, reduce. It makes asynchronously processing arrays simple and makes the code easier to understand:

var listOfParams = [];

for (var i = 0; i < 10; i++) {
    // Construct an array of params:
    listOfParams.push({someParam:i});
}

async.map(listOfParams,someAsyncFunction,function (err,result) {
    console.log(result);
});

Another thing async gives you is different algorithms for how to process the asynchronous tasks. Say for example you want to scrape a website but don't want them to ban your IP address for spamming their server. You can use async.series() instead of parallel to process the tasks one at a time:

// Set-up listOfAsyncFunctions as above

async.series(listOfAsyncFunctions,function (err,result) {
    console.log(result); // result will be the same order as listOfAsyncFunctions
});

Or if you want to process 3 tasks at a time:

async. parallelLimit(listOfAsyncFunctions, 3, function (err,result) {
    console.log(result); // result will be the same order as listOfAsyncFunctions
});

Promise.all()

The Promise.all() method works in a similar way to async.parallel() only it works with promises instead. You construct an array of promises then pass them to Promise.all():

var listOfPromises = [];

for (var i = 0; i < 10; i++) {
    // Construct an array of promises
    listOfPromises.push(somePromiseFunction({someParam:i}));
}

Promise.all(listOfPromises).then(function(result){
    console.log(result);
});