I wrote this as a convenient way to batch together multiple, repetitive AJAX calls. I was working in angular at the time, so I use the $q
service, but I'm sure it could be adapted for a more general promise framework. I find it so useful, I'm interested in polishing it for general consumption.
// A utility function that can run for-like loops with asynchronous processes.
// Use just like forEach, but you can launch something like an ajax call in the
// loop, as long as you return a promise. You can choose to run the calls in
// serial (one after the other) or in parallel (launching them all at once).
// Either way, you can attach a callback when they're all done, and an array of
// the promise resolve values of each step is available.
// For example, this would call doAsyncJob() for every item in myList, one
// after the other, and run doSomethingElse() once those were all done:
// $forEachAsync(myList, function (item) {
// return doAsyncJob(item);
// }, 'serial').then(function (arrayOfJobResults) {
// doSomethingElse();
// });
MyAngularApp.factory('$forEachAsync', function ($q) {
'use strict';
return function forEachAsync(arrayOrDict, f, serialOrParallel) {
if (serialOrParallel === 'parallel') {
// Iterate over the data, calling f immediately for each data
// point. Collect all the resulting promises together for return
// so further code can be executed when all the calls are done.
return $q.all(forEach(arrayOrDict, f));
}
if (serialOrParallel === 'serial') {
// Set up a deferred we control as a zeroth link in the chain,
// which makes writing the loop easier.
var serialDeferred = $q.defer(),
serialPromise = serialDeferred.promise,
returnValues = [];
// Do NOT make all the calls immediately, instead embed each data
// point and chain them in a series of `then`s.
// Note: this makes the assumption that forEach iterates over both
// arrays and objects by providing only two arguments.
forEach(arrayOrDict, function (a, b) {
serialPromise = serialPromise.then(function (value) {
returnValues.push(value);
return f(a, b);
});
});
// Fire off the chain.
serialDeferred.resolve();
// Return the whole chain so further code can extend it, making
// sure to return the resolve values of each iteration as a list.
return serialPromise.then(function (value) {
// Add the final resolve value, and slice off the first, which
// is the "zeroth link" value and is always undefined.
returnValues.push(value);
return returnValues.slice(1);
});
}
throw new Error(
"Must be 'serial' or 'parallel', got " + serialOrParallel
);
};
});
returnValues
in its entirety in theforEach
loop without post-processing it. \$\endgroup\$