diff options
| author | Steve Robb <softnfuzzyrobb@gmail.com> | 2014-08-17 14:38:14 +0100 |
|---|---|---|
| committer | Steve Robb <softnfuzzyrobb@gmail.com> | 2014-08-31 11:35:46 +0100 |
| commit | e56a37170b017c3bade81eedab57a99a5e2f019f (patch) | |
| tree | 971aab1648632aab6b2995d506d23b2949c32d4f | |
| parent | 232121251f601c9be7209f6382ee32180df676f1 (diff) | |
| download | async-e56a37170b017c3bade81eedab57a99a5e2f019f.tar.gz | |
autoInject - dependency injection for auto.
| -rw-r--r-- | README.md | 68 | ||||
| -rwxr-xr-x | lib/async.js | 34 | ||||
| -rwxr-xr-x | test/test-async.js | 165 |
3 files changed, 265 insertions, 2 deletions
@@ -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(); }); }; |
