diff options
-rw-r--r-- | CHANGELOG.md | 1 | ||||
-rw-r--r-- | README.md | 81 | ||||
-rw-r--r-- | lib/index.js | 6 | ||||
-rw-r--r-- | lib/internal/createTester.js | 3 | ||||
-rw-r--r-- | lib/reflect.js | 26 | ||||
-rw-r--r-- | lib/reflectAll.js | 7 | ||||
-rw-r--r-- | mocha_test/detect.js | 14 | ||||
-rw-r--r-- | mocha_test/some.js | 14 | ||||
-rwxr-xr-x | test/test-async.js | 125 |
9 files changed, 277 insertions, 0 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 7370b49..1ac1ec9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ Another theme is performance. We have eliminated internal deferrals in all cases - Added `race`, analogous to `Promise.race()`. It will run an array of async tasks in parallel and will call its callback with the result of the first task to respond. (#568, #1038) - Collection methods now accept ES2015 iterators. Maps, Sets, and anything that implements the iterator spec can now be passed directly to `each`, `map`, `parallel`, etc.. (#579, #839, #1074) - Added `timeout`, a wrapper for an async function that will make the task time-out after the specified time. (#1007, #1027) +- Added `reflect` and `reflectAll`, analagous to [`Promise.reflect()`](http://bluebirdjs.com/docs/api/reflect.html), a wrapper for async tasks that always succeeds, by gathering results and errors into an object. (#942, #1012, #1095) - `constant` supports dynamic arguments -- it will now always use its last argument as the callback. (#1016, #1052) - `setImmediate` and `nextTick` now support arguments to partially apply to the deferred function, like the node-native versions do. (#940, #1053) - Added `autoInject`, a relative of `auto` that automatically spreads a task's dependencies as arguments to the task function. (#608, #1055) @@ -264,6 +264,8 @@ Some functions are also available in the following forms: * [`dir`](#dir) * [`noConflict`](#noConflict) * [`timeout`](#timeout) +* [`reflect`](#reflect) +* [`reflectAll`](#reflectAll) ## Collections @@ -2095,3 +2097,82 @@ async.timeout(function(callback) { doAsyncTask(callback); }, 1000); ``` + +--------------------------------------- + +<a name="reflect"></a> +### reflect(function) + +Wraps the function in another function that always returns data even when it errors. +The object returned has either the property `error` or `value`. + +__Arguments__ + +* `function` - The function you want to wrap + +__Example__ + +```js +async.parallel([ + async.reflect(function(callback){ + // do some stuff ... + callback(null, 'one'); + }), + async.reflect(function(callback){ + // do some more stuff but error ... + callback('bad stuff happened'); + }), + async.reflect(function(callback){ + // do some more stuff ... + callback(null, 'two'); + }) +], +// optional callback +function(err, results){ + // values + // results[0].value = 'one' + // results[1].error = 'bad stuff happened' + // results[2].value = 'two' +}); +``` + +--------------------------------------- + +<a name="reflectAll"></a> +### reflectAll() + +A helper function that wraps an array of functions with reflect. + +__Arguments__ + +* `tasks` - The array of functions to wrap in reflect. + +__Example__ + +```javascript +let tasks = [ + function(callback){ + setTimeout(function(){ + callback(null, 'one'); + }, 200); + }, + function(callback){ + // do some more stuff but error ... + callback(new Error('bad stuff happened')); + } + function(callback){ + setTimeout(function(){ + callback(null, 'two'); + }, 100); + } +]; + +async.parallel(async.reflectAll(tasks), +// optional callback +function(err, results){ + // values + // results[0].value = 'one' + // results[1].error = Error('bad stuff happened') + // results[2].value = 'two' +}); +``` diff --git a/lib/index.js b/lib/index.js index e3eb48d..6832662 100644 --- a/lib/index.js +++ b/lib/index.js @@ -47,7 +47,9 @@ import queue from './queue'; import race from './race'; import reduce from './reduce'; import reduceRight from './reduceRight'; +import reflect from './reflect'; import reject from './reject'; +import reflectAll from './reflectAll'; import rejectLimit from './rejectLimit'; import rejectSeries from './rejectSeries'; import retry from './retry'; @@ -117,6 +119,8 @@ export default { race: race, reduce: reduce, reduceRight: reduceRight, + reflect: reflect, + reflectAll: reflectAll, reject: reject, rejectLimit: rejectLimit, rejectSeries: rejectSeries, @@ -205,6 +209,8 @@ export { race as race, reduce as reduce, reduceRight as reduceRight, + reflect as reflect, + reflectAll as reflectAll, reject as reject, rejectLimit as rejectLimit, rejectSeries as rejectSeries, diff --git a/lib/internal/createTester.js b/lib/internal/createTester.js index 662f5e8..ac38bf7 100644 --- a/lib/internal/createTester.js +++ b/lib/internal/createTester.js @@ -1,4 +1,5 @@ 'use strict'; +import noop from 'lodash/noop'; export default function _createTester(eachfn, check, getResult) { return function(arr, limit, iteratee, cb) { @@ -27,9 +28,11 @@ export default function _createTester(eachfn, check, getResult) { }); } if (arguments.length > 3) { + cb = cb || noop; eachfn(arr, limit, wrappedIteratee, done); } else { cb = iteratee; + cb = cb || noop; iteratee = limit; eachfn(arr, wrappedIteratee, done); } diff --git a/lib/reflect.js b/lib/reflect.js new file mode 100644 index 0000000..2711254 --- /dev/null +++ b/lib/reflect.js @@ -0,0 +1,26 @@ +import initialParams from './internal/initialParams'; +import rest from 'lodash/rest'; + +export default function reflect(fn) { + return initialParams(function reflectOn(args, reflectCallback) { + args.push(rest(function callback(err, cbArgs) { + if (err) { + reflectCallback(null, { + error: err + }); + } else { + var value = null; + if (cbArgs.length === 1) { + value = cbArgs[0]; + } else if (cbArgs.length > 1) { + value = cbArgs; + } + reflectCallback(null, { + value: value + }); + } + })); + + return fn.apply(this, args); + }); +} diff --git a/lib/reflectAll.js b/lib/reflectAll.js new file mode 100644 index 0000000..c4ecd9f --- /dev/null +++ b/lib/reflectAll.js @@ -0,0 +1,7 @@ +'use strict'; + +import reflect from './reflect'; + +export default function reflectAll(tasks) { + return tasks.map(reflect); +} diff --git a/mocha_test/detect.js b/mocha_test/detect.js index 77c68c5..ddc86ce 100644 --- a/mocha_test/detect.js +++ b/mocha_test/detect.js @@ -72,6 +72,20 @@ describe("detect", function () { }, 50); }); + it('detect no callback', function(done) { + var calls = []; + + async.detect([1, 2, 3], function (val, cb) { + calls.push(val); + cb(); + }); + + setTimeout(function () { + expect(calls).to.eql([1, 2, 3]); + done(); + }, 10); + }); + it('detectSeries - ensure stop', function (done) { async.detectSeries([1, 2, 3, 4, 5], function (num, cb) { if (num > 3) throw new Error("detectSeries did not stop iterating"); diff --git a/mocha_test/some.js b/mocha_test/some.js index d2c6e77..0a5a8da 100644 --- a/mocha_test/some.js +++ b/mocha_test/some.js @@ -49,6 +49,20 @@ describe("some", function () { }); }); + it('some no callback', function(done) { + var calls = []; + + async.some([1, 2, 3], function (val, cb) { + calls.push(val); + cb(); + }); + + setTimeout(function () { + expect(calls).to.eql([1, 2, 3]); + done(); + }, 10); + }); + it('someLimit true', function(done){ async.someLimit([3,1,2], 2, function(x, callback){ setTimeout(function(){callback(null, x === 2);}, 0); diff --git a/test/test-async.js b/test/test-async.js index 6e11b5a..6831ac2 100755 --- a/test/test-async.js +++ b/test/test-async.js @@ -491,6 +491,29 @@ exports['parallel call in another context'] = function(test) { vm.runInNewContext(fn, sandbox); }; +exports['parallel error with reflect'] = function(test){ + async.parallel([ + async.reflect(function(callback){ + callback('error', 1); + }), + async.reflect(function(callback){ + callback('error2', 2); + }), + async.reflect(function(callback){ + callback(null, 2); + }) + ], + function(err, results){ + test.ok(err === null, err + " passed instead of 'null'"); + test.same(results, [ + { error: 'error' }, + { error: 'error2' }, + { value: 2 } + ]); + test.done(); + }); +}; + exports['parallel does not continue replenishing after error'] = function (test) { var started = 0; var arr = [ @@ -558,6 +581,40 @@ exports['series'] = { }); }, + 'with reflect': function(test){ + var call_order = []; + async.series([ + async.reflect(function(callback){ + setTimeout(function(){ + call_order.push(1); + callback(null, 1); + }, 25); + }), + async.reflect(function(callback){ + setTimeout(function(){ + call_order.push(2); + callback(null, 2); + }, 50); + }), + async.reflect(function(callback){ + setTimeout(function(){ + call_order.push(3); + callback(null, 3,3); + }, 15); + }) + ], + function(err, results){ + test.ok(err === null, err + " passed instead of 'null'"); + test.deepEqual(results, [ + { value: 1 }, + { value: 2 }, + { value: [3,3] } + ]); + test.same(call_order, [1,2,3]); + test.done(); + }); +}, + 'empty array': function(test){ async.series([], function(err, results){ test.equals(err, null); @@ -583,6 +640,30 @@ exports['series'] = { setTimeout(test.done, 100); }, + 'error with reflect': function(test){ + test.expect(2); + async.series([ + async.reflect(function(callback){ + callback('error', 1); + }), + async.reflect(function(callback){ + callback('error2', 2); + }), + async.reflect(function(callback){ + callback(null, 1); + }) + ], + function(err, results){ + test.ok(err === null, err + " passed instead of 'null'"); + test.deepEqual(results, [ + { error: 'error' }, + { error: 'error2' }, + { value: 1 } + ]); + test.done(); + }); +}, + 'no callback': function(test){ async.series([ function(callback){callback();}, @@ -1334,6 +1415,50 @@ exports['map'] = { }); }, + 'with reflect': function(test){ + var call_order = []; + async.map([1,3,2], async.reflect(function(item, cb) { + setTimeout(function(){ + call_order.push(item); + cb(null, item*2); + }, item*25); + }), function(err, results){ + test.ok(err === null, err + " passed instead of 'null'"); + test.same(call_order, [1,2,3]); + test.same(results, [ + { value: 2 }, + { value: 6 }, + { value: 4 } + ]); + test.done(); + }); +}, + + 'error with reflect': function(test){ + var call_order = []; + async.map([-1,1,3,2], async.reflect(function(item, cb) { + setTimeout(function(){ + call_order.push(item); + if (item < 0) { + cb('number less then zero'); + } else { + cb(null, item*2); + } + + }, item*25); + }), function(err, results){ + test.ok(err === null, err + " passed instead of 'null'"); + test.same(call_order, [-1,1,2,3]); + test.same(results, [ + { error: 'number less then zero' }, + { value: 2 }, + { value: 6 }, + { value: 4 } + ]); + test.done(); + }); +}, + 'map original untouched': function(test){ var a = [1,2,3]; async.map(a, function(x, callback){ |