summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Robb <softnfuzzyrobb@gmail.com>2014-08-17 14:38:14 +0100
committerSteve Robb <softnfuzzyrobb@gmail.com>2014-08-31 11:35:46 +0100
commite56a37170b017c3bade81eedab57a99a5e2f019f (patch)
tree971aab1648632aab6b2995d506d23b2949c32d4f
parent232121251f601c9be7209f6382ee32180df676f1 (diff)
downloadasync-e56a37170b017c3bade81eedab57a99a5e2f019f.tar.gz
autoInject - dependency injection for auto.
-rw-r--r--README.md68
-rwxr-xr-xlib/async.js34
-rwxr-xr-xtest/test-async.js165
3 files changed, 265 insertions, 2 deletions
diff --git a/README.md b/README.md
index 8f4e98c..60ed04a 100644
--- a/README.md
+++ b/README.md
@@ -147,6 +147,7 @@ Usage:
* [`priorityQueue`](#priorityQueue)
* [`cargo`](#cargo)
* [`auto`](#auto)
+* [`autoInject`](#autoInject)
* [`retry`](#retry)
* [`iterator`](#iterator)
* [`apply`](#apply)
@@ -1344,6 +1345,73 @@ 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" />
### retry([times = 5], task, [callback])
diff --git a/lib/async.js b/lib/async.js
index a13f835..4d2d69f 100755
--- a/lib/async.js
+++ b/lib/async.js
@@ -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 = [];
diff --git a/test/test-async.js b/test/test-async.js
index 6a35606..8180119 100755
--- a/test/test-async.js
+++ b/test/test-async.js
@@ -454,7 +454,6 @@ exports['auto results'] = function(test){
});
};
-
exports['auto empty object'] = function(test){
async.auto({}, function(err){
test.done();
@@ -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();
});
};