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

autoInject - dependency injection for auto. #608

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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ Usage:
* [`priorityQueue`](#priorityQueue)
* [`cargo`](#cargo)
* [`auto`](#auto)
* [`autoInject`](#autoInject)
* [`retry`](#retry)
* [`iterator`](#iterator)
* [`apply`](#apply)
Expand Down Expand Up @@ -1342,6 +1343,73 @@ For a complicated series of `async` tasks, using the [`auto`](#auto) function ma
new tasks much easier (and the code more readable).


---------------------------------------

<a name="autoInject" />
### autoInject(tasks, [callback])

A dependency-injected version of the [`auto`](#auto) function. Dependent tasks are
specified as parameters to the function, after the usual callback parameter, with the
parameter names matching the names of the tasks it depends on. This can provide even more
readable task graphs which can be easier to maintain.

If a final callback is specified, the task results are similarly injected, specified as
named parameters after the initial error parameter.

The autoInject function is purely syntactic sugar and its semantics are otherwise
equivalent to [`auto`](#auto).

__Arguments__

* `tasks` - An object, each of whose properties is a function of the form
'func(callback, [dependencies...]). The object's key of a property serves as the
name of the task defined by that property, i.e. can be used when specifying requirements
for other tasks.
The `callback` parameter is a `callback(err, result)` which must be called when finished,
passing an `error` (which can be `null`) and the result of the function's execution.
The remaining parameters name other tasks on which the task is dependent, and the results
from those tasks are the arguments of those parameters.
* `callback(err, [results...])` - An optional callback which is called when all the
tasks have been completed. It receives the `err` argument if any `tasks`
pass an error to their callback. The remaining parameters are task names whose results
you are interested in. This callback will only be called when all tasks have finished or
an error has occurred, and so do not not specify dependencies in the same way as `tasks`
do. If an error occurs, no further `tasks` will be performed, and `results` will only be
valid for those tasks which managed to complete.


__Example__

The example from [`auto`](#auto) can be rewritten as follows:

```js
async.autoInject({
get_data: function(callback){
// async code to get some data
callback(null, 'data', 'converted to array');
},
make_folder: function(callback){
// async code to create a directory to store a file in
// this is run at the same time as getting the data
callback(null, 'folder');
},
write_file: function(callback, get_data, make_folder){
// once there is some data and the directory exists,
// write the data to a file in the directory
callback(null, 'filename');
},
email_link: function(callback, write_file){
// once the file is written let's email a link to it...
// write_file contains the filename returned by write_file.
callback(null, {'file':write_file, 'email':'user@example.com'});
}
}, function(err, email_link) {
console.log('err = ', err);
console.log('email_link = ', email_link);
});
```


---------------------------------------

<a name="retry" />
Expand Down
34 changes: 34 additions & 0 deletions lib/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,40 @@
});
};

var _argsRegEx = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var _diArgs = function(func) {
var result = func.toString().match(_argsRegEx)[1].split(',');
for (var i = 0, e = result.length; i != e; ++i)
result[i] = result[i].trim();
return result;
};

async.autoInject = function(tasks, callback) {
var injTasks = {};
for (var i in tasks)
injTasks[i] = (function(val) {
if (!(val instanceof Function))
return val;

var args = _diArgs(val);
if (args.length < 2)
return val;

args.shift();

return args.concat(function(cb, results) {
val.apply(null, [cb].concat(_map(args, function(a) { return results[a]; })));
});
})(tasks[i]);

return this.auto(
injTasks,
callback && function(err, results) {
callback.apply(null, [err].concat(_map(_diArgs(callback).slice(1), function(a) { return results[a]; })));
}
);
};

async.retry = function(times, task, callback) {
var DEFAULT_TIMES = 5;
var attempts = [];
Expand Down
165 changes: 163 additions & 2 deletions test/test-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,6 @@ exports['auto results'] = function(test){
});
};


exports['auto empty object'] = function(test){
async.auto({}, function(err){
test.done();
Expand Down Expand Up @@ -504,7 +503,169 @@ exports['auto error should pass partial results'] = function(test) {
test.equals(err, 'testerror');
test.equals(results.task1, 'result1');
test.equals(results.task2, 'result2');
test.done();
test.done();
});
};

exports['autoInject'] = function(test){
var callOrder = [];
var testdata = [{test: 'test'}];
async.autoInject({
task1: function(callback, task2){
setTimeout(function(){
callOrder.push('task1');
callback();
}, 25);
},
task2: function(callback){
setTimeout(function(){
callOrder.push('task2');
callback();
}, 50);
},
task3: function(callback, task2){
callOrder.push('task3');
callback();
},
task4: function(callback, task1, task2){
callOrder.push('task4');
callback();
},
task5: function(callback, task2){
setTimeout(function(){
callOrder.push('task5');
callback();
}, 0);
},
task6: function(callback, task2){
callOrder.push('task6');
callback();
}
},
function(err){
test.same(callOrder, ['task2','task6','task3','task5','task1','task4']);
test.done();
});
};

exports['autoInject petrify'] = function (test) {
var callOrder = [];
async.autoInject({
task1: function (callback, task2) {
setTimeout(function () {
callOrder.push('task1');
callback();
}, 100);
},
task2: function (callback) {
setTimeout(function () {
callOrder.push('task2');
callback();
}, 200);
},
task3: function (callback, task2) {
callOrder.push('task3');
callback();
},
task4: function (callback, task1, task2) {
callOrder.push('task4');
callback();
}
},
function (err) {
test.same(callOrder, ['task2', 'task3', 'task1', 'task4']);
test.done();
});
};

exports['autoInject results'] = function(test){
var callOrder = [];
async.autoInject({
task1: function(callback, task2){
test.same(task2, 'task2');
setTimeout(function(){
callOrder.push('task1');
callback(null, 'task1a', 'task1b');
}, 25);
},
task2: function(callback){
setTimeout(function(){
callOrder.push('task2');
callback(null, 'task2');
}, 50);
},
task3: function(callback, task2){
test.same(task2, 'task2');
callOrder.push('task3');
callback(null);
},
task4: function(callback, task1, task2){
test.same(task1, ['task1a','task1b']);
test.same(task2, 'task2');
callOrder.push('task4');
callback(null, 'task4');
}
},
function(err, task2, task4, task3, task1){
test.same(callOrder, ['task2','task3','task1','task4']);
test.same(task1, ['task1a','task1b']);
test.same(task2, 'task2');
test.same(task3, undefined);
test.same(task4, 'task4');
test.done();
});
};

exports['autoInject empty object'] = function(test){
async.autoInject({}, function(err){
test.done();
});
};

exports['autoInject error'] = function(test){
test.expect(1);
async.autoInject({
task1: function(callback){
callback('testerror');
},
task2: function(callback, task1){
test.ok(false, 'task2 should not be called');
callback();
},
task3: function(callback){
callback('testerror2');
}
},
function(err){
test.equals(err, 'testerror');
});
setTimeout(test.done, 100);
};

exports['autoInject no callback'] = function(test){
async.autoInject({
task1: function(callback){callback();},
task2: function(callback, task1){callback(); test.done();}
});
};

exports['autoInject error should pass partial results'] = function(test) {
async.autoInject({
task1: function(callback){
callback(false, 'result1');
},
task2: function(callback, task1){
callback('testerror', 'result2');
},
task3: function(callback, task2){
test.ok(false, 'task3 should not be called');
}
},
function(err, task1, task2){
test.equals(err, 'testerror');
test.equals(task1, 'result1');
test.equals(task2, 'result2');
test.done();
});
};

Expand Down