(function(exports) { "use strict"; exports.batchAsync = function(limit, arr, doStuff) { arr = arr.slice(0); return new Promise(function(resolve, reject) { var total = arr.length; var active = 0; var results = []; var error; function doMoreStuff() { // Don't take on any more tasks if we've errored, // or if too many are already in progress if (error || active > limit) { return; } // If there are no more tasks to start, return if (!arr.length) { // If everything is also *finished*, resolve if (active < 1) { resolve(results); } return; } // We need to dequeue the task here so the index is correct // (keep in mind we want to support sync and async) var index = total - arr.length; var task = arr.shift(); active += 1; // Spawn another task immediately, // which will be stopped if we're at the limit doMoreStuff(); var p; try { p = doStuff(task, index, arr); } catch (e) { // we need to handle, and bubble, synchronous errors error = e; reject(e); throw e; } // Do stuff and then decrease the active counter when done // add support for sync by rapping in a promise Promise.resolve(p) .then(function(result) { if ("undefined" === typeof result) { throw new Error( "result was 'undefined'. Please return 'null' to signal that you didn't just forget to return another promise." ); } active -= 1; results[index] = result; }) .then(doMoreStuff) .catch(function(e) { // handle async errors error = e; reject(e); }); } doMoreStuff(); }); }; })("undefined" !== typeof window ? window : module.exports);