Skip to content

Commit 83ded82

Browse files
committed
Add reflect that wraps a function with a always passing callback and a object with error or value property set.
This is one way to solve issue #942.
1 parent 01205e0 commit 83ded82

File tree

3 files changed

+277
-5
lines changed

3 files changed

+277
-5
lines changed

README.md

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,8 @@ Some functions are also available in the following forms:
229229
* [`log`](#log)
230230
* [`dir`](#dir)
231231
* [`noConflict`](#noConflict)
232+
* [`reflect`](#reflect)
233+
* [`reflectAll`](#reflectAll)
232234
233235
## Collections
234236
@@ -1446,7 +1448,7 @@ __Arguments__
14461448
* `opts` - Can be either an object with `times` and `interval` or a number.
14471449
* `times` - The number of attempts to make before giving up. The default is `5`.
14481450
* `interval` - The time to wait between retries, in milliseconds. The default is `0`.
1449-
* If `opts` is a number, the number specifies the number of times to retry, with the default interval of `0`.
1451+
* If `opts` is a number, the number specifies the number of times to retry, with the default interval of `0`.
14501452
* `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)`
14511453
which must be called when finished, passing `err` (which can be `null`) and the `result` of
14521454
the function's execution, and (2) a `results` object, containing the results of
@@ -1464,14 +1466,14 @@ async.retry(3, apiMethod, function(err, result) {
14641466
```
14651467
14661468
```js
1467-
// try calling apiMethod 3 times, waiting 200 ms between each retry
1469+
// try calling apiMethod 3 times, waiting 200 ms between each retry
14681470
async.retry({times: 3, interval: 200}, apiMethod, function(err, result) {
14691471
// do something with the result
14701472
});
14711473
```
14721474
14731475
```js
1474-
// try calling apiMethod the default 5 times no delay between each retry
1476+
// try calling apiMethod the default 5 times no delay between each retry
14751477
async.retry(apiMethod, function(err, result) {
14761478
// do something with the result
14771479
});
@@ -1792,7 +1794,7 @@ async.waterfall([
17921794
return db.model.create(contents);
17931795
}),
17941796
function (model, next) {
1795-
// `model` is the instantiated model object.
1797+
// `model` is the instantiated model object.
17961798
// If there was an error, this function would be skipped.
17971799
}
17981800
], callback)
@@ -1875,3 +1877,82 @@ node> async.dir(hello, 'world');
18751877
18761878
Changes the value of `async` back to its original value, returning a reference to the
18771879
`async` object.
1880+
1881+
---------------------------------------
1882+
1883+
<a name="reflect"></a>
1884+
### reflect(function)
1885+
1886+
Wraps the function in another function that always returns data even when it errors.
1887+
The object returns ether has a property of error or value.
1888+
1889+
__Arguments__
1890+
1891+
* `function` - The function you want to wrap
1892+
1893+
__Example__
1894+
1895+
```js
1896+
async.parallel([
1897+
async.reflect(function(callback){
1898+
// do some stuff ...
1899+
callback(null, 'one');
1900+
}),
1901+
async.reflect(function(callback){
1902+
// do some more stuff but error ...
1903+
callback('bad stuff happened');
1904+
}),
1905+
async.reflect(function(callback){
1906+
// do some more stuff ...
1907+
callback(null, 'two');
1908+
})
1909+
],
1910+
// optional callback
1911+
function(err, results){
1912+
// values
1913+
// results[0].value = 'one'
1914+
// results[1].error = 'bad stuff happened'
1915+
// results[2].value = 'two'
1916+
});
1917+
```
1918+
1919+
---------------------------------------
1920+
1921+
<a name="reflectAll"></a>
1922+
### reflectAll()
1923+
1924+
A helper function that wraps an array of functions with reflect.
1925+
1926+
__Arguments__
1927+
1928+
* `tasks` - The array of functions to wrap in reflect.
1929+
1930+
__Example__
1931+
1932+
```javascript
1933+
let tasks = [
1934+
function(callback){
1935+
setTimeout(function(){
1936+
callback(null, 'one');
1937+
}, 200);
1938+
},
1939+
function(callback){
1940+
// do some more stuff but error ...
1941+
callback(new Error('bad stuff happened'));
1942+
}
1943+
function(callback){
1944+
setTimeout(function(){
1945+
callback(null, 'two');
1946+
}, 100);
1947+
}
1948+
];
1949+
1950+
async.parallel(async.reflectAll(tasks),
1951+
// optional callback
1952+
function(err, results){
1953+
// values
1954+
// results[0].value = 'one'
1955+
// results[1].error = Error('bad stuff happened')
1956+
// results[2].value = 'two'
1957+
});
1958+
```

lib/async.js

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,7 @@
10901090
var memoized = _restParam(function memoized(args) {
10911091
var callback = args.pop();
10921092
var key = hasher.apply(null, args);
1093-
if (has.call(memo, key)) {
1093+
if (has.call(memo, key)) {
10941094
async.setImmediate(function () {
10951095
callback.apply(null, memo[key]);
10961096
});
@@ -1247,6 +1247,38 @@
12471247
});
12481248
};
12491249

1250+
async.reflect = function(fn) {
1251+
return function reflectOn() {
1252+
var args = Array.prototype.slice.call(arguments);
1253+
var reflectCallback = args.pop();
1254+
1255+
args.push(function callback(err) {
1256+
if (err) {
1257+
reflectCallback(null, {
1258+
error: err
1259+
});
1260+
} else {
1261+
var cbArgs = Array.prototype.slice.call(arguments, 1);
1262+
var value = null;
1263+
if (cbArgs.length === 1) {
1264+
value = cbArgs[0];
1265+
} else if (cbArgs.length > 1) {
1266+
value = cbArgs;
1267+
}
1268+
reflectCallback(null, {
1269+
value: value
1270+
});
1271+
}
1272+
});
1273+
1274+
return fn.apply(this, args);
1275+
};
1276+
};
1277+
1278+
async.reflectAll = function(tasks) {
1279+
return tasks.map(async.reflect);
1280+
};
1281+
12501282
// Node.js
12511283
if (typeof module === 'object' && module.exports) {
12521284
module.exports = async;

test/test-async.js

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,40 @@ exports['parallel'] = function(test){
906906
});
907907
};
908908

909+
exports['parallel with reflect'] = function(test){
910+
var call_order = [];
911+
async.parallel([
912+
async.reflect(function(callback){
913+
setTimeout(function(){
914+
call_order.push(1);
915+
callback(null, 1);
916+
}, 50);
917+
}),
918+
async.reflect(function(callback){
919+
setTimeout(function(){
920+
call_order.push(2);
921+
callback(null, 2);
922+
}, 100);
923+
}),
924+
async.reflect(function(callback){
925+
setTimeout(function(){
926+
call_order.push(3);
927+
callback(null, 3,3);
928+
}, 25);
929+
})
930+
],
931+
function(err, results){
932+
test.ok(err === null, err + " passed instead of 'null'");
933+
test.same(call_order, [3,1,2]);
934+
test.same(results, [
935+
{ value: 1 },
936+
{ value: 2 },
937+
{ value: [3,3] }
938+
]);
939+
test.done();
940+
});
941+
};
942+
909943
exports['parallel empty array'] = function(test){
910944
async.parallel([], function(err, results){
911945
test.ok(err === null, err + " passed instead of 'null'");
@@ -929,6 +963,29 @@ exports['parallel error'] = function(test){
929963
setTimeout(test.done, 100);
930964
};
931965

966+
exports['parallel error with reflect'] = function(test){
967+
async.parallel([
968+
async.reflect(function(callback){
969+
callback('error', 1);
970+
}),
971+
async.reflect(function(callback){
972+
callback('error2', 2);
973+
}),
974+
async.reflect(function(callback){
975+
callback(null, 2);
976+
})
977+
],
978+
function(err, results){
979+
test.ok(err === null, err + " passed instead of 'null'");
980+
test.same(results, [
981+
{ error: 'error' },
982+
{ error: 'error2' },
983+
{ value: 2 }
984+
]);
985+
test.done();
986+
});
987+
};
988+
932989
exports['parallel no callback'] = function(test){
933990
async.parallel([
934991
function(callback){callback();},
@@ -1155,6 +1212,40 @@ exports['series'] = {
11551212
});
11561213
},
11571214

1215+
'with reflect': function(test){
1216+
var call_order = [];
1217+
async.series([
1218+
async.reflect(function(callback){
1219+
setTimeout(function(){
1220+
call_order.push(1);
1221+
callback(null, 1);
1222+
}, 25);
1223+
}),
1224+
async.reflect(function(callback){
1225+
setTimeout(function(){
1226+
call_order.push(2);
1227+
callback(null, 2);
1228+
}, 50);
1229+
}),
1230+
async.reflect(function(callback){
1231+
setTimeout(function(){
1232+
call_order.push(3);
1233+
callback(null, 3,3);
1234+
}, 15);
1235+
})
1236+
],
1237+
function(err, results){
1238+
test.ok(err === null, err + " passed instead of 'null'");
1239+
test.deepEqual(results, [
1240+
{ value: 1 },
1241+
{ value: 2 },
1242+
{ value: [3,3] }
1243+
]);
1244+
test.same(call_order, [1,2,3]);
1245+
test.done();
1246+
});
1247+
},
1248+
11581249
'empty array': function(test){
11591250
async.series([], function(err, results){
11601251
test.equals(err, null);
@@ -1180,6 +1271,30 @@ exports['series'] = {
11801271
setTimeout(test.done, 100);
11811272
},
11821273

1274+
'error with reflect': function(test){
1275+
test.expect(2);
1276+
async.series([
1277+
async.reflect(function(callback){
1278+
callback('error', 1);
1279+
}),
1280+
async.reflect(function(callback){
1281+
callback('error2', 2);
1282+
}),
1283+
async.reflect(function(callback){
1284+
callback(null, 1);
1285+
})
1286+
],
1287+
function(err, results){
1288+
test.ok(err === null, err + " passed instead of 'null'");
1289+
test.deepEqual(results, [
1290+
{ error: 'error' },
1291+
{ error: 'error2' },
1292+
{ value: 1 }
1293+
]);
1294+
test.done();
1295+
});
1296+
},
1297+
11831298
'no callback': function(test){
11841299
async.series([
11851300
function(callback){callback();},
@@ -1841,6 +1956,50 @@ exports['map'] = {
18411956
});
18421957
},
18431958

1959+
'with reflect': function(test){
1960+
var call_order = [];
1961+
async.map([1,3,2], async.reflect(function(item, cb) {
1962+
setTimeout(function(){
1963+
call_order.push(item);
1964+
cb(null, item*2);
1965+
}, item*25);
1966+
}), function(err, results){
1967+
test.ok(err === null, err + " passed instead of 'null'");
1968+
test.same(call_order, [1,2,3]);
1969+
test.same(results, [
1970+
{ value: 2 },
1971+
{ value: 6 },
1972+
{ value: 4 }
1973+
]);
1974+
test.done();
1975+
});
1976+
},
1977+
1978+
'error with reflect': function(test){
1979+
var call_order = [];
1980+
async.map([-1,1,3,2], async.reflect(function(item, cb) {
1981+
setTimeout(function(){
1982+
call_order.push(item);
1983+
if (item < 0) {
1984+
cb('number less then zero');
1985+
} else {
1986+
cb(null, item*2);
1987+
}
1988+
1989+
}, item*25);
1990+
}), function(err, results){
1991+
test.ok(err === null, err + " passed instead of 'null'");
1992+
test.same(call_order, [-1,1,2,3]);
1993+
test.same(results, [
1994+
{ error: 'number less then zero' },
1995+
{ value: 2 },
1996+
{ value: 6 },
1997+
{ value: 4 }
1998+
]);
1999+
test.done();
2000+
});
2001+
},
2002+
18442003
'map original untouched': function(test){
18452004
var a = [1,2,3];
18462005
async.map(a, function(x, callback){

0 commit comments

Comments
 (0)