summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md1
-rw-r--r--README.md81
-rw-r--r--lib/index.js6
-rw-r--r--lib/internal/createTester.js3
-rw-r--r--lib/reflect.js26
-rw-r--r--lib/reflectAll.js7
-rw-r--r--mocha_test/detect.js14
-rw-r--r--mocha_test/some.js14
-rwxr-xr-xtest/test-async.js125
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)
diff --git a/README.md b/README.md
index 3009118..f18239c 100644
--- a/README.md
+++ b/README.md
@@ -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){