diff --git a/lib/async.js b/lib/async.js index 5f2fb189e..f1ef20934 100755 --- a/lib/async.js +++ b/lib/async.js @@ -57,6 +57,14 @@ return memo; }; + var _forEachOf = function (object, iterator) { + for (key in object) { + if (object.hasOwnProperty(key)) { + iterator(object[key], key); + } + } + }; + var _keys = function (obj) { if (Object.keys) { return Object.keys(obj); @@ -111,6 +119,28 @@ }); }; + async.forEachOf = function (object, iterator, callback) { + callback = callback || function () {}; + var size = object.length || _keys(object).length; + var completed = 0 + if (!size) { + return callback(); + } + _forEachOf(object, function (value, key) { + iterator(object[key], key, function (err) { + if (err) { + callback(err); + callback = function () {}; + } else { + completed += 1; + if (completed === size) { + callback(null); + } + } + }); + }); + }; + async.forEachSeries = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { @@ -144,6 +174,42 @@ iterate(); }; + async.forEachOfSeries = function (obj, iterator, callback) { + callback = callback || function () {}; + var keys = _keys(obj); + var size = keys.length; + if (!size) { + return callback(); + } + var completed = 0; + var iterate = function () { + var sync = true; + var key = keys[completed]; + iterator(obj[key], key, function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= size) { + callback(null); + } + else { + if (sync) { + async.nextTick(iterate); + } + else { + iterate(); + } + } + } + }); + sync = false; + }; + iterate(); + }; + async.forEachLimit = function (arr, limit, iterator, callback) { var fn = _forEachLimit(limit); fn.apply(null, [arr, iterator, callback]); @@ -190,6 +256,55 @@ }; + async.forEachOfLimit = function (obj, limit, iterator, callback) { + var fn = obj.constructor === Array ? _forEachOfLimit(limit) : _forEachOfLimit(limit); + fn.apply(null, [obj, iterator, callback]); + }; + + var _forEachOfLimit = function (limit) { + + return function (obj, iterator, callback) { + callback = callback || function () {}; + var keys = _keys(obj); + var size = keys.length; + if (!size || limit <= 0) { + return callback(); + } + var completed = 0; + var started = 0; + var running = 0; + + (function replenish () { + if (completed >= size) { + return callback(); + } + + while (running < limit && started < size) { + started += 1; + running += 1; + var key = keys[started - 1]; + iterator(obj[key], key, function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + running -= 1; + if (completed >= size) { + callback(); + } + else { + replenish(); + } + } + }); + } + })(); + }; + }; + + var doParallel = function (fn) { return function () { var args = Array.prototype.slice.call(arguments); diff --git a/test/test-async.js b/test/test-async.js index 323527a99..cad8bb0ea 100755 --- a/test/test-async.js +++ b/test/test-async.js @@ -17,6 +17,13 @@ function forEachIterator(args, x, callback) { }, x*25); } +function forEachOfIterator(args, value, key, callback) { + setTimeout(function(){ + args.push(key, value); + callback(); + }, value*25); +} + function mapIterator(call_order, x, callback) { setTimeout(function(){ call_order.push(x); @@ -43,6 +50,13 @@ function forEachNoCallbackIterator(test, x, callback) { test.done(); } +function forEachOfNoCallbackIterator(test, x, key, callback) { + test.equal(x, 1); + test.equal(key, "a"); + callback(); + test.done(); +} + function getFunctionsObject(call_order) { return { one: function(callback){ @@ -652,6 +666,47 @@ exports['forEach no callback'] = function(test){ async.forEach([1], forEachNoCallbackIterator.bind(this, test)); }; +exports['forEachOf'] = function(test){ + var args = []; + async.forEachOf({ a: 1, b: 2 }, forEachOfIterator.bind(this, args), function(err){ + test.same(args, ["a", 1, "b", 2]); + test.done(); + }); +}; + +exports['forEachOf empty object'] = function(test){ + test.expect(1); + async.forEachOf({}, function(value, key, callback){ + test.ok(false, 'iterator should not be called'); + callback(); + }, function(err) { + test.ok(true, 'should call callback'); + }); + setTimeout(test.done, 25); +}; + +exports['forEachOf error'] = function(test){ + test.expect(1); + async.forEachOf({ a: 1, b: 2 }, function(value, key, callback) { + callback('error'); + }, function(err){ + test.equals(err, 'error'); + }); + setTimeout(test.done, 50); +}; + +exports['forEachOf no callback'] = function(test){ + async.forEachOf({ a: 1 }, forEachOfNoCallbackIterator.bind(this, test)); +}; + +exports['forEachOf with array'] = function(test){ + var args = []; + async.forEachOf([ "a", "b" ], forEachOfIterator.bind(this, args), function(err){ + test.same(args, [0, "a", 1, "b"]); + test.done(); + }); +}; + exports['forEachSeries'] = function(test){ var args = []; async.forEachSeries([1,3,2], forEachIterator.bind(this, args), function(err){ @@ -688,6 +743,50 @@ exports['forEachSeries no callback'] = function(test){ async.forEachSeries([1], forEachNoCallbackIterator.bind(this, test)); }; +exports['forEachOfSeries'] = function(test){ + var args = []; + async.forEachOfSeries({ a: 1, b: 2 }, forEachOfIterator.bind(this, args), function(err){ + test.same(args, [ "a", 1, "b", 2 ]); + test.done(); + }); +}; + +exports['forEachOfSeries empty object'] = function(test){ + test.expect(1); + async.forEachOfSeries({}, function(x, callback){ + test.ok(false, 'iterator should not be called'); + callback(); + }, function(err){ + test.ok(true, 'should call callback'); + }); + setTimeout(test.done, 25); +}; + +exports['forEachOfSeries error'] = function(test){ + test.expect(2); + var call_order = []; + async.forEachOfSeries({ a: 1, b: 2 }, function(value, key, callback){ + call_order.push(value, key); + callback('error'); + }, function(err){ + test.same(call_order, [ 1, "a" ]); + test.equals(err, 'error'); + }); + setTimeout(test.done, 50); +}; + +exports['forEachOfSeries no callback'] = function(test){ + async.forEachOfSeries({ a: 1 }, forEachOfNoCallbackIterator.bind(this, test)); +}; + +exports['forEachOfSeries with array'] = function(test){ + var args = []; + async.forEachOfSeries([ "a", "b" ], forEachOfIterator.bind(this, args), function(err){ + test.same(args, [ 0, "a", 1, "b" ]); + test.done(); + }); +}; + exports['forEachLimit'] = function(test){ var args = []; var arr = [0,1,2,3,4,5,6,7,8,9]; @@ -775,6 +874,99 @@ exports['forEachLimit synchronous'] = function(test){ }); }; +exports['forEachOfLimit'] = function(test){ + var args = []; + var obj = { a: 1, b: 2, c: 3, d: 4 }; + async.forEachOfLimit(obj, 2, function(value, key, callback){ + setTimeout(function(){ + args.push(value, key); + callback(); + }, value * 5); + }, function(err){ + test.same(args, [ 1, "a", 2, "b", 3, "c", 4, "d" ]); + test.done(); + }); +}; + +exports['forEachOfLimit empty object'] = function(test){ + test.expect(1); + async.forEachOfLimit({}, 2, function(value, key, callback){ + test.ok(false, 'iterator should not be called'); + callback(); + }, function(err){ + test.ok(true, 'should call callback'); + }); + setTimeout(test.done, 25); +}; + +exports['forEachOfLimit limit exceeds size'] = function(test){ + var args = []; + var obj = { a: 1, b: 2, c: 3, d: 4, e: 5 }; + async.forEachOfLimit(obj, 10, forEachOfIterator.bind(this, args), function(err){ + test.same(args, [ "a", 1, "b", 2, "c", 3, "d", 4, "e", 5 ]); + test.done(); + }); +}; + +exports['forEachOfLimit limit equal size'] = function(test){ + var args = []; + var obj = { a: 1, b: 2, c: 3, d: 4, e: 5 }; + async.forEachOfLimit(obj, 5, forEachOfIterator.bind(this, args), function(err){ + test.same(args, [ "a", 1, "b", 2, "c", 3, "d", 4, "e", 5 ]); + test.done(); + }); +}; + +exports['forEachOfLimit zero limit'] = function(test){ + test.expect(1); + async.forEachOfLimit({ a: 1, b: 2 }, 0, function(x, callback){ + test.ok(false, 'iterator should not be called'); + callback(); + }, function(err){ + test.ok(true, 'should call callback'); + }); + setTimeout(test.done, 25); +}; + +exports['forEachOfLimit error'] = function(test){ + test.expect(2); + var obj = { a: 1, b: 2, c: 3, d: 4, e: 5 }; + var call_order = []; + + async.forEachOfLimit(obj, 3, function(value, key, callback){ + call_order.push(value, key); + if (value === 2) { + callback('error'); + } + }, function(err){ + test.same(call_order, [ 1, "a", 2, "b" ]); + test.equals(err, 'error'); + }); + setTimeout(test.done, 25); +}; + +exports['forEachOfLimit no callback'] = function(test){ + async.forEachOfLimit({ a: 1 }, 1, forEachOfNoCallbackIterator.bind(this, test)); +}; + +exports['forEachOfLimit synchronous'] = function(test){ + var args = []; + var obj = { a: 1, b: 2 }; + async.forEachOfLimit(obj, 5, forEachOfIterator.bind(this, args), function(err){ + test.same(args, [ "a", 1, "b", 2 ]); + test.done(); + }); +}; + +exports['forEachOfLimit with array'] = function(test){ + var args = []; + var arr = [ "a", "b" ] + async.forEachOfLimit(arr, 1, forEachOfIterator.bind(this, args), function (err) { + test.same(args, [ 0, "a", 1, "b" ]); + test.done(); + }); +}; + exports['map'] = function(test){ var call_order = []; async.map([1,3,2], mapIterator.bind(this, call_order), function(err, results){