Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add reflect function wrapper to allow always passing iterations #1012

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 85 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ Some functions are also available in the following forms:
* [`log`](#log)
* [`dir`](#dir)
* [`noConflict`](#noConflict)
* [`reflect`](#reflect)
* [`reflectAll`](#reflectAll)
## Collections
Expand Down Expand Up @@ -1446,7 +1448,7 @@ __Arguments__
* `opts` - Can be either an object with `times` and `interval` or a number.
* `times` - The number of attempts to make before giving up. The default is `5`.
* `interval` - The time to wait between retries, in milliseconds. The default is `0`.
* If `opts` is a number, the number specifies the number of times to retry, with the default interval of `0`.
* If `opts` is a number, the number specifies the number of times to retry, with the default interval of `0`.
* `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)`
which must be called when finished, passing `err` (which can be `null`) and the `result` of
the function's execution, and (2) a `results` object, containing the results of
Expand All @@ -1464,14 +1466,14 @@ async.retry(3, apiMethod, function(err, result) {
```
```js
// try calling apiMethod 3 times, waiting 200 ms between each retry
// try calling apiMethod 3 times, waiting 200 ms between each retry
async.retry({times: 3, interval: 200}, apiMethod, function(err, result) {
// do something with the result
});
```
```js
// try calling apiMethod the default 5 times no delay between each retry
// try calling apiMethod the default 5 times no delay between each retry
async.retry(apiMethod, function(err, result) {
// do something with the result
});
Expand Down Expand Up @@ -1792,7 +1794,7 @@ async.waterfall([
return db.model.create(contents);
}),
function (model, next) {
// `model` is the instantiated model object.
// `model` is the instantiated model object.
// If there was an error, this function would be skipped.
}
], callback)
Expand Down Expand Up @@ -1875,3 +1877,82 @@ node> async.dir(hello, 'world');
Changes the value of `async` back to its original value, returning a reference to the
`async` object.
---------------------------------------
<a name="reflect"></a>
### reflect(function)
Wraps the function in another function that always returns data even when it errors.
The object returns ether has a property of 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'
});
```
34 changes: 33 additions & 1 deletion lib/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,7 @@
var memoized = _restParam(function memoized(args) {
var callback = args.pop();
var key = hasher.apply(null, args);
if (has.call(memo, key)) {
if (has.call(memo, key)) {
async.setImmediate(function () {
callback.apply(null, memo[key]);
});
Expand Down Expand Up @@ -1247,6 +1247,38 @@
});
};

async.reflect = function(fn) {
return function reflectOn() {
var args = Array.prototype.slice.call(arguments);
var reflectCallback = args.pop();

args.push(function callback(err) {
if (err) {
reflectCallback(null, {
error: err
});
} else {
var cbArgs = Array.prototype.slice.call(arguments, 1);
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);
};
};

async.reflectAll = function(tasks) {
return tasks.map(async.reflect);
};

// Node.js
if (typeof module === 'object' && module.exports) {
module.exports = async;
Expand Down
159 changes: 159 additions & 0 deletions test/test-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -906,6 +906,40 @@ exports['parallel'] = function(test){
});
};

exports['parallel with reflect'] = function(test){
var call_order = [];
async.parallel([
async.reflect(function(callback){
setTimeout(function(){
call_order.push(1);
callback(null, 1);
}, 50);
}),
async.reflect(function(callback){
setTimeout(function(){
call_order.push(2);
callback(null, 2);
}, 100);
}),
async.reflect(function(callback){
setTimeout(function(){
call_order.push(3);
callback(null, 3,3);
}, 25);
})
],
function(err, results){
test.ok(err === null, err + " passed instead of 'null'");
test.same(call_order, [3,1,2]);
test.same(results, [
{ value: 1 },
{ value: 2 },
{ value: [3,3] }
]);
test.done();
});
};

exports['parallel empty array'] = function(test){
async.parallel([], function(err, results){
test.ok(err === null, err + " passed instead of 'null'");
Expand All @@ -929,6 +963,29 @@ exports['parallel error'] = function(test){
setTimeout(test.done, 100);
};

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 no callback'] = function(test){
async.parallel([
function(callback){callback();},
Expand Down Expand Up @@ -1155,6 +1212,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);
Expand All @@ -1180,6 +1271,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();},
Expand Down Expand Up @@ -1841,6 +1956,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){
Expand Down