diff --git a/.gitignore b/.gitignore index 8225baa4a..1d9dd9d2d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /node_modules /dist +/.idea \ No newline at end of file diff --git a/README.md b/README.md index 17164760b..bffbbc8a8 100644 --- a/README.md +++ b/README.md @@ -73,6 +73,7 @@ So far its been tested in IE6, IE7, IE8, FF3.6 and Chrome 5. Usage: ### Collections * [each](#each) +* [eachIndex](#eachIndex) * [map](#map) * [filter](#filter) * [reject](#reject) @@ -131,7 +132,7 @@ __Arguments__ * arr - An array to iterate over. * iterator(item, callback) - A function to apply to each item in the array. The iterator is passed a callback(err) which must be called once it has - completed. If no error has occured, the callback should be run without + completed. If no error has occurred, the callback should be run without arguments or with an explicit null argument. * callback(err) - A callback which is called after all the iterator functions have finished, or an error has occurred. @@ -147,6 +148,47 @@ async.each(openFiles, saveFile, function(err){ }); ``` + + +### eachIndex(arr, iterator, callback) + +Just like `each`, applies an iterator function to each item in an array, +in parallel. The iterator is called with an item from the list, the position +an item is at in the supplied array, and a callback for when it has finished. +If the iterator passes an error to this callback, the main callback for the +`eachIndex` function is immediately called with the error. + +Note, that since this function applies the iterator to each item in parallel +there is no guarantee that the iterator functions will complete in order. + +__Arguments__ + +* arr - An array to iterate over. +* iterator(item, index, callback) - A function to apply to each item in + the array. The iterator is passed an `index` position of the item in the + array. Useful when it is necessary to distinguish each item based on its + position in the array. Also the iterator is passed a `callback(err)` + which must be called once it has completed. If no error has occurred, + the callback should be run without arguments or with an explicit null + argument. +* callback(err) - A callback which is called after all the iterator functions + have finished, or an error has occurred. + +__Example__ + +```js +var itemsArr = ['apple', 'banana', 'orange']; + +function iterator(val, index, callback) { + // do something with `val` based on its `index`, + // then call `callback`. +} + +async.eachIndex(itemsArr, iterator, function(err){ + // if any of the iterators produced an error, err would equal that error +}); +``` + --------------------------------------- @@ -177,7 +219,7 @@ __Arguments__ * limit - The maximum number of iterators to run at any time. * iterator(item, callback) - A function to apply to each item in the array. The iterator is passed a callback(err) which must be called once it has - completed. If no error has occured, the callback should be run without + completed. If no error has occurred, the callback should be run without arguments or with an explicit null argument. * callback(err) - A callback which is called after all the iterator functions have finished, or an error has occurred. diff --git a/lib/async.js b/lib/async.js index ad3982a56..0ee8ca4a3 100755 --- a/lib/async.js +++ b/lib/async.js @@ -112,6 +112,29 @@ }; async.forEach = async.each; + async.eachIndex = function (arr, iterator, callback) { + callback = callback || function () {}; + if (!arr.length) { + return callback(); + } + var completed = 0; + _each(arr, function (x, ix) { + iterator(x, ix, only_once(function (err) { + if (err) { + callback(err); + callback = function () {}; + } + else { + completed += 1; + if (completed >= arr.length) { + callback(null); + } + } + })); + }); + }; + async.forEachIndex = async.eachIndex; + async.eachSeries = function (arr, iterator, callback) { callback = callback || function () {}; if (!arr.length) { diff --git a/test/test-async.js b/test/test-async.js index 5bd72bf4e..8b6d63967 100755 --- a/test/test-async.js +++ b/test/test-async.js @@ -17,6 +17,13 @@ function eachIterator(args, x, callback) { }, x*25); } +function eachIndexIterator(args, x, ix, callback) { + setTimeout(function(){ + args.push(x); + callback(); + }, x*25); +} + function mapIterator(call_order, x, callback) { setTimeout(function(){ call_order.push(x); @@ -43,6 +50,12 @@ function eachNoCallbackIterator(test, x, callback) { test.done(); } +function eachIndexNoCallbackIterator(test, x, ix, callback) { + test.equal(x, 1); + callback(); + test.done(); +} + function getFunctionsObject(call_order) { return { one: function(callback){ @@ -824,6 +837,56 @@ exports['forEach alias'] = function (test) { test.done(); }; +exports['eachIndex'] = function(test){ + var args = []; + async.eachIndex([1,3,2], eachIndexIterator.bind(this, args), function(err){ + test.same(args, [1,2,3]); + test.done(); + }); +}; + +exports['eachIndex extra callback'] = function(test){ + var count = 0; + async.eachIndex([1,3,2], function(val, index, callback) { + count++; + callback(); + test.throws(callback); + if (count == 3) { + test.done(); + } + }); +}; + +exports['eachIndex empty array'] = function(test){ + test.expect(1); + async.eachIndex([], function(x, ix, callback){ + test.ok(false, 'iterator should not be called'); + callback(); + }, function(err){ + test.ok(true, 'should call callback'); + }); + setTimeout(test.done, 25); +}; + +exports['eachIndex error'] = function(test){ + test.expect(1); + async.eachIndex([1,2,3], function(x, ix, callback){ + callback('error'); + }, function(err){ + test.equals(err, 'error'); + }); + setTimeout(test.done, 50); +}; + +exports['eachIndex no callback'] = function(test){ + async.eachIndex([1], eachIndexNoCallbackIterator.bind(this, test)); +}; + +exports['forEachIndex alias'] = function (test) { + test.strictEqual(async.eachIndex, async.forEachIndex); + test.done(); +}; + exports['eachSeries'] = function(test){ var args = []; async.eachSeries([1,3,2], eachIterator.bind(this, args), function(err){