summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md100
-rw-r--r--lib/autoInject.js46
-rw-r--r--lib/constant.js5
-rw-r--r--lib/index.js6
-rw-r--r--lib/internal/setImmediate.js18
-rw-r--r--lib/nextTick.js4
-rw-r--r--lib/retry.js90
-rw-r--r--lib/timeout.js36
-rw-r--r--lib/waterfall.js33
-rw-r--r--mocha_test/autoInject.js77
-rw-r--r--mocha_test/constant.js28
-rw-r--r--mocha_test/nextTick.js38
-rw-r--r--mocha_test/retry.js101
-rw-r--r--mocha_test/setImmediate.js24
-rw-r--r--mocha_test/timeout.js48
-rw-r--r--mocha_test/waterfall.js148
-rwxr-xr-xtest/test-async.js298
17 files changed, 775 insertions, 325 deletions
diff --git a/README.md b/README.md
index 02f9418..b872bc8 100644
--- a/README.md
+++ b/README.md
@@ -219,6 +219,7 @@ Some functions are also available in the following forms:
* [`queue`](#queue), [`priorityQueue`](#priorityQueue)
* [`cargo`](#cargo)
* [`auto`](#auto)
+* [`autoInject`](#autoInject)
* [`retry`](#retry)
* [`iterator`](#iterator)
* [`times`](#times), `timesSeries`, `timesLimit`
@@ -237,6 +238,7 @@ Some functions are also available in the following forms:
* [`log`](#log)
* [`dir`](#dir)
* [`noConflict`](#noConflict)
+* [`timeout`](#timeout)
## Collections
@@ -1290,7 +1292,7 @@ methods:
* `saturated` - A callback that is called when the `queue.length()` hits the concurrency and further tasks will be queued.
* `empty` - A callback that is called when the last item from the `queue` is given to a `worker`.
* `drain` - A callback that is called when the last item from the `queue` has returned from the `worker`.
-* `idle()`, `pause()`, `resume()`, `kill()` - cargo inherits all of the same methods and event calbacks as [`queue`](#queue)
+* `idle()`, `pause()`, `resume()`, `kill()` - cargo inherits all of the same methods and event callbacks as [`queue`](#queue)
__Example__
@@ -1420,6 +1422,73 @@ function(err, results){
For a complicated series of `async` tasks, using the [`auto`](#auto) function makes adding 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([dependencies...], callback). 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(get_data, make_folder, callback){
+ // once there is some data and the directory exists,
+ // write the data to a file in the directory
+ callback(null, 'filename');
+ },
+ email_link: function(write_file, callback){
+ // 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);
+});
+```
+
+If you are using a JS minifier that mangles parameter names, `autoInject` will not work with plain functions, since the parameter names will be collapsed to a single letter identifier. To work around this, you can explicitly specify the names of the parameters your task function needs in an array, similar to Angular.js dependency injection.
+
+```js
+async.autoInject({
+ //...
+ write_file: ['get_data', 'make_folder', function(get_data, make_folder, callback){
+ callback(null, 'filename');
+ }],
+ email_link: ['write_file', function(write_file, callback){
+ callback(null, {'file':write_file, 'email':'user@example.com'});
+ }]
+ //...
+},
+```
+
+This still has an advantage over plain `auto`, since the results a task depends on are still spread into arguments.
+
+
+---------------------------------------
<a name="retry"></a>
### retry([opts = {times: 5, interval: 0}| 5], task, [callback])
@@ -1567,7 +1636,7 @@ three
---------------------------------------
<a name="nextTick"></a>
-### nextTick(callback), setImmediate(callback)
+### nextTick(callback, [args...]), setImmediate(callback, [args...])
Calls `callback` on a later loop around the event loop. In Node.js this just
calls `process.nextTick`; in the browser it falls back to `setImmediate(callback)`
@@ -1579,6 +1648,7 @@ This is used internally for browser-compatibility purposes.
__Arguments__
* `callback` - The function to call on a later loop around the event loop.
+* `args...` - any number of additional arguments to pass to the callback on the next tick
__Example__
@@ -1589,6 +1659,10 @@ async.nextTick(function(){
// call_order now equals ['one','two']
});
call_order.push('one')
+
+async.setImmediate(function (a, b, c) {
+ // a, b, and c equal 1, 2, and 3
+}, 1, 2, 3)
```
---------------------------------------
@@ -1903,3 +1977,25 @@ node> async.dir(hello, 'world');
Changes the value of `async` back to its original value, returning a reference to the
`async` object.
+
+---------------------------------------
+
+<a name="timeout"></a>
+### timeout(function, miliseconds)
+
+Sets a time limit on an asynchronous function. If the function does not call its callback within the specified miliseconds, it will be called with a timeout error. The code property for the error object will be `'ETIMEDOUT'`.
+
+Returns a wrapped function that can be used with any of the control flow functions.
+
+__Arguments__
+
+* `function` - The asynchronous function you want to set the time limit.
+* `miliseconds` - The specified time limit.
+
+__Example__
+
+```js
+async.timeout(function(callback) {
+ doAsyncTask(callback);
+}, 1000);
+```
diff --git a/lib/autoInject.js b/lib/autoInject.js
new file mode 100644
index 0000000..f84a4d9
--- /dev/null
+++ b/lib/autoInject.js
@@ -0,0 +1,46 @@
+import auto from './auto';
+import forOwn from 'lodash/forOwn';
+import arrayMap from 'lodash/_arrayMap';
+import clone from 'lodash/_baseClone';
+import isArray from 'lodash/isArray';
+
+var argsRegex = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
+
+function parseParams(func) {
+ return func.toString().match(argsRegex)[1].split(/\s*\,\s*/);
+}
+
+export default function autoInject(tasks, callback) {
+ var newTasks = {};
+
+ forOwn(tasks, function (taskFn, key) {
+ var params;
+
+ if (isArray(taskFn)) {
+ params = clone(taskFn);
+ taskFn = params.pop();
+
+ newTasks[key] = clone(params).concat(newTask);
+ } else if (taskFn.length === 0) {
+ throw new Error("autoInject task functions require explicit parameters.");
+ } else if (taskFn.length === 1) {
+ // no dependencies, use the function as-is
+ newTasks[key] = taskFn;
+ } else {
+ params = parseParams(taskFn);
+ params.pop();
+
+ newTasks[key] = clone(params).concat(newTask);
+ }
+
+ function newTask(results, taskCb) {
+ var newArgs = arrayMap(params, function (name) {
+ return results[name];
+ });
+ newArgs.push(taskCb);
+ taskFn.apply(null, newArgs);
+ }
+ });
+
+ auto(newTasks, callback);
+}
diff --git a/lib/constant.js b/lib/constant.js
index 706507f..ecaff80 100644
--- a/lib/constant.js
+++ b/lib/constant.js
@@ -4,7 +4,8 @@ import rest from 'lodash/rest';
export default rest(function(values) {
var args = [null].concat(values);
- return function (cb) {
- return cb.apply(this, args);
+ return function () {
+ var callback = [].slice.call(arguments).pop();
+ return callback.apply(this, args);
};
});
diff --git a/lib/index.js b/lib/index.js
index 2e27f25..3db4064 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -5,6 +5,7 @@ import applyEachSeries from './applyEachSeries';
import apply from './apply';
import asyncify from './asyncify';
import auto from './auto';
+import autoInject from './autoInject';
import cargo from './cargo';
import compose from './compose';
import concat from './concat';
@@ -55,6 +56,7 @@ import setImmediate from './setImmediate';
import some from './some';
import someLimit from './someLimit';
import sortBy from './sortBy';
+import timeout from './timeout';
import times from './times';
import timesLimit from './timesLimit';
import timesSeries from './timesSeries';
@@ -70,6 +72,7 @@ export default {
apply: apply,
asyncify: asyncify,
auto: auto,
+ autoInject: autoInject,
cargo: cargo,
compose: compose,
concat: concat,
@@ -120,6 +123,7 @@ export default {
some: some,
someLimit: someLimit,
sortBy: sortBy,
+ timeout: timeout,
times: times,
timesLimit: timesLimit,
timesSeries: timesSeries,
@@ -153,6 +157,7 @@ export {
apply as apply,
asyncify as asyncify,
auto as auto,
+ autoInject as autoInject,
cargo as cargo,
compose as compose,
concat as concat,
@@ -203,6 +208,7 @@ export {
some as some,
someLimit as someLimit,
sortBy as sortBy,
+ timeout as timeout,
times as times,
timesLimit as timesLimit,
timesSeries as timesSeries,
diff --git a/lib/internal/setImmediate.js b/lib/internal/setImmediate.js
index c02ad71..7d15249 100644
--- a/lib/internal/setImmediate.js
+++ b/lib/internal/setImmediate.js
@@ -1,19 +1,21 @@
'use strict';
+import rest from 'lodash/rest';
var _setImmediate = typeof setImmediate === 'function' && setImmediate;
-var _delay;
+var _defer;
if (_setImmediate) {
- _delay = function(fn) {
- // not a direct alias for IE10 compatibility
- _setImmediate(fn);
- };
+ _defer = _setImmediate;
} else if (typeof process === 'object' && typeof process.nextTick === 'function') {
- _delay = process.nextTick;
+ _defer = process.nextTick;
} else {
- _delay = function(fn) {
+ _defer = function(fn) {
setTimeout(fn, 0);
};
}
-export default _delay;
+export default rest(function (fn, args) {
+ _defer(function () {
+ fn.apply(null, args);
+ });
+});
diff --git a/lib/nextTick.js b/lib/nextTick.js
index b61a8b4..05d9555 100644
--- a/lib/nextTick.js
+++ b/lib/nextTick.js
@@ -2,6 +2,4 @@
import setImmediate from './internal/setImmediate';
-var nexTick = typeof process === 'object' && typeof process.nextTick === 'function' ? process.nextTick : setImmediate;
-
-export default nexTick;
+export default setImmediate;
diff --git a/lib/retry.js b/lib/retry.js
index 506a424..aecd07f 100644
--- a/lib/retry.js
+++ b/lib/retry.js
@@ -1,12 +1,12 @@
'use strict';
import series from './series';
+import noop from 'lodash/noop';
export default function retry(times, task, callback) {
var DEFAULT_TIMES = 5;
var DEFAULT_INTERVAL = 0;
- var attempts = [];
var opts = {
times: DEFAULT_TIMES,
@@ -14,64 +14,62 @@ export default function retry(times, task, callback) {
};
function parseTimes(acc, t) {
- if (typeof t === 'number') {
- acc.times = parseInt(t, 10) || DEFAULT_TIMES;
- } else if (typeof t === 'object') {
- acc.times = parseInt(t.times, 10) || DEFAULT_TIMES;
- acc.interval = parseInt(t.interval, 10) || DEFAULT_INTERVAL;
+ if (typeof t === 'object') {
+ acc.times = +t.times || DEFAULT_TIMES;
+ acc.interval = +t.interval || DEFAULT_INTERVAL;
+ } else if (typeof t === 'number' || typeof t === 'string') {
+ acc.times = +t || DEFAULT_TIMES;
} else {
- throw new Error('Unsupported argument type for \'times\': ' + typeof t);
+ throw new Error("Invalid arguments for async.retry");
}
}
- var length = arguments.length;
- if (length < 1 || length > 3) {
- throw new Error('Invalid arguments - must be either (task), (task, callback), (times, task) or (times, task, callback)');
- } else if (length <= 2 && typeof times === 'function') {
- callback = task;
+
+ if (arguments.length < 3 && typeof times === 'function') {
+ callback = task || noop;
task = times;
- }
- if (typeof times !== 'function') {
+ } else {
parseTimes(opts, times);
+ callback = callback || noop;
}
- opts.callback = callback;
- opts.task = task;
- function wrappedTask(wrappedCallback, wrappedResults) {
- function retryAttempt(task, finalAttempt) {
- return function(seriesCallback) {
- task(function(err, result) {
- seriesCallback(!err || finalAttempt, {
- err: err,
- result: result
- });
- }, wrappedResults);
- };
- }
- function retryInterval(interval) {
- return function(seriesCallback) {
- setTimeout(function() {
- seriesCallback(null);
- }, interval);
- };
- }
+ if (typeof task !== 'function') {
+ throw new Error("Invalid arguments for async.retry");
+ }
- while (opts.times) {
- var finalAttempt = !(opts.times -= 1);
- attempts.push(retryAttempt(opts.task, finalAttempt));
- if (!finalAttempt && opts.interval > 0) {
- attempts.push(retryInterval(opts.interval));
- }
+ var attempts = [];
+ while (opts.times) {
+ var isFinalAttempt = !(opts.times -= 1);
+ attempts.push(retryAttempt(isFinalAttempt));
+ if (!isFinalAttempt && opts.interval > 0) {
+ attempts.push(retryInterval(opts.interval));
}
+ }
+
+ series(attempts, function(done, data) {
+ data = data[data.length - 1];
+ callback(data.err, data.result);
+ });
- series(attempts, function(done, data) {
- data = data[data.length - 1];
- (wrappedCallback || opts.callback)(data.err, data.result);
- });
+
+ function retryAttempt(isFinalAttempt) {
+ return function(seriesCallback) {
+ task(function(err, result) {
+ seriesCallback(!err || isFinalAttempt, {
+ err: err,
+ result: result
+ });
+ });
+ };
}
- // If a callback is passed, run this as a controll flow
- return opts.callback ? wrappedTask() : wrappedTask;
+ function retryInterval(interval) {
+ return function(seriesCallback) {
+ setTimeout(function() {
+ seriesCallback(null);
+ }, interval);
+ };
+ }
}
diff --git a/lib/timeout.js b/lib/timeout.js
new file mode 100644
index 0000000..c8c9dfd
--- /dev/null
+++ b/lib/timeout.js
@@ -0,0 +1,36 @@
+'use strict';
+
+export default function timeout(asyncFn, miliseconds) {
+ var originalCallback, timer;
+ var timedOut = false;
+
+ function injectedCallback() {
+ if (!timedOut) {
+ originalCallback.apply(null, arguments);
+ clearTimeout(timer);
+ }
+ }
+
+ function timeoutCallback() {
+ var error = new Error('Callback function timed out.');
+ error.code = 'ETIMEDOUT';
+ timedOut = true;
+ originalCallback(error);
+ }
+
+ function injectCallback(asyncFnArgs) {
+ // replace callback in asyncFn args
+ var args = Array.prototype.slice.call(asyncFnArgs, 0);
+ originalCallback = args[args.length - 1];
+ args[args.length - 1] = injectedCallback;
+ return args;
+ }
+
+ function wrappedFn() {
+ // setup timer and call original function
+ timer = setTimeout(timeoutCallback, miliseconds);
+ asyncFn.apply(null, injectCallback(arguments));
+ }
+
+ return wrappedFn;
+}
diff --git a/lib/waterfall.js b/lib/waterfall.js
index 8867c87..a31b3b4 100644
--- a/lib/waterfall.js
+++ b/lib/waterfall.js
@@ -5,28 +5,31 @@ import noop from 'lodash/noop';
import once from 'lodash/once';
import rest from 'lodash/rest';
-import ensureAsync from './ensureAsync';
-import iterator from './iterator';
+import onlyOnce from './internal/onlyOnce';
export default function(tasks, cb) {
cb = once(cb || noop);
if (!isArray(tasks)) return cb(new Error('First argument to waterfall must be an array of functions'));
if (!tasks.length) return cb();
+ var taskIndex = 0;
- function wrapIterator(iterator) {
- return rest(function(err, args) {
+ function nextTask(args) {
+ if (taskIndex === tasks.length) {
+ return cb(null, ...args);
+ }
+
+ var taskCallback = onlyOnce(rest(function(err, args) {
if (err) {
- cb.apply(null, [err].concat(args));
- } else {
- var next = iterator.next();
- if (next) {
- args.push(wrapIterator(next));
- } else {
- args.push(cb);
- }
- ensureAsync(iterator).apply(null, args);
+ return cb(err, ...args);
}
- });
+ nextTask(args);
+ }));
+
+ args.push(taskCallback);
+
+ var task = tasks[taskIndex++];
+ task(...args);
}
- wrapIterator(iterator(tasks))();
+
+ nextTask([]);
}
diff --git a/mocha_test/autoInject.js b/mocha_test/autoInject.js
new file mode 100644
index 0000000..53a3d17
--- /dev/null
+++ b/mocha_test/autoInject.js
@@ -0,0 +1,77 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+var _ = require('lodash');
+
+describe('autoInject', function () {
+
+ it("basics", function (done) {
+ var callOrder = [];
+ async.autoInject({
+ task1: function(task2, callback){
+ expect(task2).to.equal(2);
+ setTimeout(function(){
+ callOrder.push('task1');
+ callback(null, 1);
+ }, 25);
+ },
+ task2: function(callback){
+ setTimeout(function(){
+ callOrder.push('task2');
+ callback(null, 2);
+ }, 50);
+ },
+ task3: function(task2, callback){
+ expect(task2).to.equal(2);
+ callOrder.push('task3');
+ callback(null, 3);
+ },
+ task4: function(task1, task2, callback){
+ expect(task1).to.equal(1);
+ expect(task2).to.equal(2);
+ callOrder.push('task4');
+ callback(null, 4);
+ },
+ task5: function(task2, callback){
+ expect(task2).to.equal(2);
+ setTimeout(function(){
+ callOrder.push('task5');
+ callback(null, 5);
+ }, 0);
+ },
+ task6: function(task2, callback){
+ expect(task2).to.equal(2);
+ callOrder.push('task6');
+ callback(null, 6);
+ }
+ },
+ function(err, results){
+ expect(results.task6).to.equal(6);
+ expect(callOrder).to.eql(['task2','task6','task3','task5','task1','task4']);
+ done();
+ });
+ });
+
+ it('should work with array tasks', function (done) {
+ var callOrder = [];
+
+ async.autoInject({
+ task1: function (cb) {
+ callOrder.push('task1');
+ cb(null, 1);
+ },
+ task2: ['task3', function (task3, cb) {
+ expect(task3).to.equal(3);
+ callOrder.push('task2');
+ cb(null, 2);
+ }],
+ task3: function (cb) {
+ callOrder.push('task3');
+ cb(null, 3);
+ }
+ }, function () {
+ expect(callOrder).to.eql(['task1','task3','task2']);
+ done();
+ });
+ });
+
+});
diff --git a/mocha_test/constant.js b/mocha_test/constant.js
new file mode 100644
index 0000000..d76076c
--- /dev/null
+++ b/mocha_test/constant.js
@@ -0,0 +1,28 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+
+describe('constant', function () {
+
+ it('basic usage', function(done){
+ var f = async.constant(42, 1, 2, 3);
+ f(function (err, value, a, b, c) {
+ expect(err).to.equal(null);
+ expect(value).to.equal(42);
+ expect(a).to.equal(1);
+ expect(b).to.equal(2);
+ expect(c).to.equal(3);
+ done();
+ });
+ });
+
+ it('called with multiple arguments', function(done){
+ var f = async.constant(42, 1, 2, 3);
+ f('argument to ignore', 'another argument', function (err, value, a) {
+ expect(err).to.equal(null);
+ expect(value).to.equal(42);
+ expect(a).to.equal(1);
+ done();
+ });
+ });
+
+});
diff --git a/mocha_test/nextTick.js b/mocha_test/nextTick.js
new file mode 100644
index 0000000..b428822
--- /dev/null
+++ b/mocha_test/nextTick.js
@@ -0,0 +1,38 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+
+describe("nextTick", function () {
+
+ it('basics', function(done){
+ var call_order = [];
+ async.nextTick(function(){call_order.push('two');});
+ call_order.push('one');
+ setTimeout(function(){
+ expect(call_order).to.eql(['one','two']);
+ done();
+ }, 50);
+ });
+
+ it('nextTick in the browser', function(done){
+ if (!process.browser) {
+ // skip this test in node
+ return done();
+ }
+
+ var call_order = [];
+ async.nextTick(function(){call_order.push('two');});
+
+ call_order.push('one');
+ setTimeout(function(){
+ expect(call_order).to.eql(['one','two']);
+ done();
+ }, 50);
+ });
+
+ it("extra args", function (done) {
+ async.nextTick(function (a, b, c) {
+ expect([a, b, c]).to.eql([1, 2, 3]);
+ done();
+ }, 1, 2, 3);
+ });
+});
diff --git a/mocha_test/retry.js b/mocha_test/retry.js
new file mode 100644
index 0000000..d0df1fe
--- /dev/null
+++ b/mocha_test/retry.js
@@ -0,0 +1,101 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+var assert = require('assert');
+
+describe("retry", function () {
+
+ // Issue 306 on github: https://github.com/caolan/async/issues/306
+ it('retry when attempt succeeds',function(done) {
+ var failed = 3;
+ var callCount = 0;
+ var expectedResult = 'success';
+ function fn(callback) {
+ callCount++;
+ failed--;
+ if (!failed) callback(null, expectedResult);
+ else callback(true); // respond with error
+ }
+ async.retry(fn, function(err, result){
+ assert(err === null, err + " passed instead of 'null'");
+ assert.equal(callCount, 3, 'did not retry the correct number of times');
+ assert.equal(result, expectedResult, 'did not return the expected result');
+ done();
+ });
+ });
+
+ it('retry when all attempts succeeds',function(done) {
+ var times = 3;
+ var callCount = 0;
+ var error = 'ERROR';
+ var erroredResult = 'RESULT';
+ function fn(callback) {
+ callCount++;
+ callback(error + callCount, erroredResult + callCount); // respond with indexed values
+ }
+ async.retry(times, fn, function(err, result){
+ assert.equal(callCount, 3, "did not retry the correct number of times");
+ assert.equal(err, error + times, "Incorrect error was returned");
+ assert.equal(result, erroredResult + times, "Incorrect result was returned");
+ done();
+ });
+ });
+
+ it('retry fails with invalid arguments',function(done) {
+ expect(function() {
+ async.retry("");
+ }).to.throw();
+ expect(function() {
+ async.retry();
+ }).to.throw();
+ expect(function() {
+ async.retry(function() {}, 2, function() {});
+ }).to.throw();
+ done();
+ });
+
+ it('retry with interval when all attempts succeeds',function(done) {
+ var times = 3;
+ var interval = 500;
+ var callCount = 0;
+ var error = 'ERROR';
+ var erroredResult = 'RESULT';
+ function fn(callback) {
+ callCount++;
+ callback(error + callCount, erroredResult + callCount); // respond with indexed values
+ }
+ var start = new Date().getTime();
+ async.retry({ times: times, interval: interval}, fn, function(err, result){
+ var now = new Date().getTime();
+ var duration = now - start;
+ assert(duration >= (interval * (times -1)), 'did not include interval');
+ assert.equal(callCount, 3, "did not retry the correct number of times");
+ assert.equal(err, error + times, "Incorrect error was returned");
+ assert.equal(result, erroredResult + times, "Incorrect result was returned");
+ done();
+ });
+ });
+
+ it("should not require a callback", function (done) {
+ var called = false;
+ async.retry(3, function(cb) {
+ called = true;
+ cb();
+ });
+ setTimeout(function () {
+ assert(called);
+ done();
+ }, 10);
+ });
+
+ it("should not require a callback and use the default times", function (done) {
+ var calls = 0;
+ async.retry(function(cb) {
+ calls++;
+ cb("fail");
+ });
+ setTimeout(function () {
+ expect(calls).to.equal(5);
+ done();
+ }, 50);
+ });
+});
diff --git a/mocha_test/setImmediate.js b/mocha_test/setImmediate.js
new file mode 100644
index 0000000..854111a
--- /dev/null
+++ b/mocha_test/setImmediate.js
@@ -0,0 +1,24 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+
+describe("setImmediate", function () {
+
+ it('basics', function(done){
+ var call_order = [];
+ async.setImmediate(function(){call_order.push('two');});
+ call_order.push('one');
+
+ setTimeout(function(){
+ expect(call_order).to.eql(['one','two']);
+ done();
+ }, 25);
+ });
+
+ it("extra args", function (done) {
+ async.setImmediate(function (a, b, c) {
+ expect([a, b, c]).to.eql([1, 2, 3]);
+ done();
+ }, 1, 2, 3);
+ });
+
+});
diff --git a/mocha_test/timeout.js b/mocha_test/timeout.js
new file mode 100644
index 0000000..8355eb6
--- /dev/null
+++ b/mocha_test/timeout.js
@@ -0,0 +1,48 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+
+describe('timeout', function () {
+
+ it('timeout with series', function(done){
+ async.series([
+ async.timeout(function asyncFn(callback) {
+ setTimeout(function() {
+ callback(null, 'I didn\'t time out');
+ }, 50);
+ }, 200),
+ async.timeout(function asyncFn(callback) {
+ setTimeout(function() {
+ callback(null, 'I will time out');
+ }, 300);
+ }, 150)
+ ],
+ function(err, results) {
+ expect(err.message).to.equal('Callback function timed out.');
+ expect(err.code).to.equal('ETIMEDOUT');
+ expect(results[0]).to.equal('I didn\'t time out');
+ done();
+ });
+ });
+
+ it('timeout with parallel', function(done){
+ async.parallel([
+ async.timeout(function asyncFn(callback) {
+ setTimeout(function() {
+ callback(null, 'I didn\'t time out');
+ }, 50);
+ }, 200),
+ async.timeout(function asyncFn(callback) {
+ setTimeout(function() {
+ callback(null, 'I will time out');
+ }, 300);
+ }, 150)
+ ],
+ function(err, results) {
+ expect(err.message).to.equal('Callback function timed out.');
+ expect(err.code).to.equal('ETIMEDOUT');
+ expect(results[0]).to.equal('I didn\'t time out');
+ done();
+ });
+ });
+
+});
diff --git a/mocha_test/waterfall.js b/mocha_test/waterfall.js
new file mode 100644
index 0000000..b1d1561
--- /dev/null
+++ b/mocha_test/waterfall.js
@@ -0,0 +1,148 @@
+var async = require('../lib');
+var expect = require('chai').expect;
+
+describe("waterfall", function () {
+
+ it('basics', function(done){
+ var call_order = [];
+ async.waterfall([
+ function(callback){
+ call_order.push('fn1');
+ setTimeout(function(){callback(null, 'one', 'two');}, 0);
+ },
+ function(arg1, arg2, callback){
+ call_order.push('fn2');
+ expect(arg1).to.equal('one');
+ expect(arg2).to.equal('two');
+ setTimeout(function(){callback(null, arg1, arg2, 'three');}, 25);
+ },
+ function(arg1, arg2, arg3, callback){
+ call_order.push('fn3');
+ expect(arg1).to.equal('one');
+ expect(arg2).to.equal('two');
+ expect(arg3).to.equal('three');
+ callback(null, 'four');
+ },
+ function(arg4, callback){
+ call_order.push('fn4');
+ expect(call_order).to.eql(['fn1','fn2','fn3','fn4']);
+ callback(null, 'test');
+ }
+ ], function(err){
+ expect(err === null, err + " passed instead of 'null'");
+ done();
+ });
+ });
+
+ it('empty array', function(done){
+ async.waterfall([], function(err){
+ if (err) throw err;
+ done();
+ });
+ });
+
+ it('non-array', function(done){
+ async.waterfall({}, function(err){
+ expect(err.message).to.equal('First argument to waterfall must be an array of functions');
+ done();
+ });
+ });
+
+ it('no callback', function(done){
+ async.waterfall([
+ function(callback){callback();},
+ function(callback){callback(); done();}
+ ]);
+ });
+
+ it('async', function(done){
+ var call_order = [];
+ async.waterfall([
+ function(callback){
+ call_order.push(1);
+ callback();
+ call_order.push(2);
+ },
+ function(callback){
+ call_order.push(3);
+ callback();
+ },
+ function(){
+ expect(call_order).to.eql([1,3]);
+ done();
+ }
+ ]);
+ });
+
+ it('error', function(done){
+ async.waterfall([
+ function(callback){
+ callback('error');
+ },
+ function(callback){
+ test.ok(false, 'next function should not be called');
+ callback();
+ }
+ ], function(err){
+ expect(err).to.equal('error');
+ done();
+ });
+ });
+
+ it('multiple callback calls', function(){
+ var arr = [
+ function(callback){
+ // call the callback twice. this should call function 2 twice
+ callback(null, 'one', 'two');
+ callback(null, 'one', 'two');
+ },
+ function(arg1, arg2, callback){
+ callback(null, arg1, arg2, 'three');
+ }
+ ];
+ expect(function () {
+ async.waterfall(arr, function () {});
+ }).to.throw(/already called/);
+ });
+
+ it('call in another context', function(done) {
+ if (process.browser) {
+ // node only test
+ done();
+ return;
+ }
+
+ var vm = require('vm');
+ var sandbox = {
+ async: async,
+ done: done
+ };
+
+ var fn = "(" + (function () {
+ async.waterfall([function (callback) {
+ callback();
+ }], function (err) {
+ if (err) {
+ return done(err);
+ }
+ done();
+ });
+ }).toString() + "())";
+
+ vm.runInNewContext(fn, sandbox);
+ });
+
+ it('should not use unnecessary deferrals', function (done) {
+ var sameStack = true;
+
+ async.waterfall([
+ function (cb) { cb(null, 1); },
+ function (arg, cb) { cb(); }
+ ], function() {
+ expect(sameStack).to.equal(true);
+ done();
+ });
+
+ sameStack = false;
+ });
+});
diff --git a/test/test-async.js b/test/test-async.js
index bd103bf..9f86acc 100755
--- a/test/test-async.js
+++ b/test/test-async.js
@@ -1,6 +1,6 @@
/**
* NOTE: We are in the process of migrating these tests to Mocha. If you are
- * adding a new test, consider creating a new spec file in mocha_tests/
+ * adding a new test, please create a new spec file in mocha_tests/
*/
require('babel-core/register');
@@ -278,77 +278,6 @@ exports['seq without callback'] = function (test) {
add2mul3.call(testcontext, 3);
};
-// Issue 306 on github: https://github.com/caolan/async/issues/306
-exports['retry when attempt succeeds'] = function(test) {
- var failed = 3;
- var callCount = 0;
- var expectedResult = 'success';
- function fn(callback) {
- callCount++;
- failed--;
- if (!failed) callback(null, expectedResult);
- else callback(true); // respond with error
- }
- async.retry(fn, function(err, result){
- test.ok(err === null, err + " passed instead of 'null'");
- test.equal(callCount, 3, 'did not retry the correct number of times');
- test.equal(result, expectedResult, 'did not return the expected result');
- test.done();
- });
-};
-
-exports['retry when all attempts succeeds'] = function(test) {
- var times = 3;
- var callCount = 0;
- var error = 'ERROR';
- var erroredResult = 'RESULT';
- function fn(callback) {
- callCount++;
- callback(error + callCount, erroredResult + callCount); // respond with indexed values
- }
- async.retry(times, fn, function(err, result){
- test.equal(callCount, 3, "did not retry the correct number of times");
- test.equal(err, error + times, "Incorrect error was returned");
- test.equal(result, erroredResult + times, "Incorrect result was returned");
- test.done();
- });
-};
-
-exports['retry fails with invalid arguments'] = function(test) {
- test.throws(function() {
- async.retry("");
- });
- test.throws(function() {
- async.retry();
- });
- test.throws(function() {
- async.retry(function() {}, 2, function() {});
- });
- test.done();
-};
-
-exports['retry with interval when all attempts succeeds'] = function(test) {
- var times = 3;
- var interval = 500;
- var callCount = 0;
- var error = 'ERROR';
- var erroredResult = 'RESULT';
- function fn(callback) {
- callCount++;
- callback(error + callCount, erroredResult + callCount); // respond with indexed values
- }
- var start = new Date().getTime();
- async.retry({ times: times, interval: interval}, fn, function(err, result){
- var now = new Date().getTime();
- var duration = now - start;
- test.ok(duration > (interval * (times -1)), 'did not include interval');
- test.equal(callCount, 3, "did not retry the correct number of times");
- test.equal(err, error + times, "Incorrect error was returned");
- test.equal(result, erroredResult + times, "Incorrect result was returned");
- test.done();
- });
-};
-
// need to fix retry, this isn't working
/*
exports['retry as an embedded task'] = function(test) {
@@ -371,7 +300,7 @@ exports['retry as an embedded task'] = function(test) {
test.equal(fooResults, retryResults, "Incorrect results were passed to retry function");
test.done();
});
-};*/
+};
exports['retry as an embedded task with interval'] = function(test) {
var start = new Date().getTime();
@@ -390,155 +319,7 @@ exports['retry as an embedded task with interval'] = function(test) {
test.ok(duration >= expectedMinimumDuration, "The duration should have been greater than " + expectedMinimumDuration + ", but was " + duration);
test.done();
});
-};
-
-exports['waterfall'] = {
-
- 'basic': function(test){
- test.expect(7);
- var call_order = [];
- async.waterfall([
- function(callback){
- call_order.push('fn1');
- setTimeout(function(){callback(null, 'one', 'two');}, 0);
- },
- function(arg1, arg2, callback){
- call_order.push('fn2');
- test.equals(arg1, 'one');
- test.equals(arg2, 'two');
- setTimeout(function(){callback(null, arg1, arg2, 'three');}, 25);
- },
- function(arg1, arg2, arg3, callback){
- call_order.push('fn3');
- test.equals(arg1, 'one');
- test.equals(arg2, 'two');
- test.equals(arg3, 'three');
- callback(null, 'four');
- },
- function(arg4, callback){
- call_order.push('fn4');
- test.same(call_order, ['fn1','fn2','fn3','fn4']);
- callback(null, 'test');
- }
- ], function(err){
- test.ok(err === null, err + " passed instead of 'null'");
- test.done();
- });
-},
-
- 'empty array': function(test){
- async.waterfall([], function(err){
- if (err) throw err;
- test.done();
- });
-},
-
- 'non-array': function(test){
- async.waterfall({}, function(err){
- test.equals(err.message, 'First argument to waterfall must be an array of functions');
- test.done();
- });
-},
-
- 'no callback': function(test){
- async.waterfall([
- function(callback){callback();},
- function(callback){callback(); test.done();}
- ]);
-},
-
- 'async': function(test){
- var call_order = [];
- async.waterfall([
- function(callback){
- call_order.push(1);
- callback();
- call_order.push(2);
- },
- function(callback){
- call_order.push(3);
- callback();
- },
- function(){
- test.same(call_order, [1,2,3]);
- test.done();
- }
- ]);
-},
-
- 'error': function(test){
- test.expect(1);
- async.waterfall([
- function(callback){
- callback('error');
- },
- function(callback){
- test.ok(false, 'next function should not be called');
- callback();
- }
- ], function(err){
- test.equals(err, 'error');
- });
- setTimeout(test.done, 50);
-},
-
- 'multiple callback calls': function(test){
- var call_order = [];
- var arr = [
- function(callback){
- call_order.push(1);
- // call the callback twice. this should call function 2 twice
- callback(null, 'one', 'two');
- callback(null, 'one', 'two');
- },
- function(arg1, arg2, callback){
- call_order.push(2);
- callback(null, arg1, arg2, 'three');
- },
- function(arg1, arg2, arg3, callback){
- call_order.push(3);
- callback(null, 'four');
- },
- function(/*arg4*/){
- call_order.push(4);
- arr[3] = function(){
- call_order.push(4);
- test.same(call_order, [1,2,2,3,3,4,4]);
- test.done();
- };
- }
- ];
- async.waterfall(arr);
-},
-
- 'call in another context': function(test) {
- if (isBrowser()) {
- // node only test
- test.done();
- return;
- }
-
- var vm = require('vm');
- var sandbox = {
- async: async,
- test: test
- };
-
- var fn = "(" + (function () {
- async.waterfall([function (callback) {
- callback();
- }], function (err) {
- if (err) {
- return test.done(err);
- }
- test.done();
- });
- }).toString() + "())";
-
- vm.runInNewContext(fn, sandbox);
-}
-
-};
+};*/
exports['parallel'] = function(test){
var call_order = [];
@@ -2042,33 +1823,6 @@ console_fn_tests('dir');
console_fn_tests('warn');
console_fn_tests('error');*/
-exports['nextTick'] = function(test){
- test.expect(1);
- var call_order = [];
- async.nextTick(function(){call_order.push('two');});
- call_order.push('one');
- setTimeout(function(){
- test.same(call_order, ['one','two']);
- test.done();
- }, 50);
-};
-
-exports['nextTick in the browser'] = function(test){
- if (!isBrowser()) {
- // skip this test in node
- return test.done();
- }
- test.expect(1);
-
- var call_order = [];
- async.nextTick(function(){call_order.push('two');});
-
- call_order.push('one');
- setTimeout(function(){
- test.same(call_order, ['one','two']);
- }, 50);
- setTimeout(test.done, 100);
-};
exports['concat'] = function(test){
test.expect(3);
@@ -3825,3 +3579,49 @@ exports['asyncify'] = {
return promises;
}, {})
};
+
+exports['timeout'] = function (test) {
+ test.expect(3);
+
+ async.series([
+ async.timeout(function asyncFn(callback) {
+ setTimeout(function() {
+ callback(null, 'I didn\'t time out');
+ }, 50);
+ }, 200),
+ async.timeout(function asyncFn(callback) {
+ setTimeout(function() {
+ callback(null, 'I will time out');
+ }, 300);
+ }, 150)
+ ],
+ function(err, results) {
+ test.ok(err.message === 'Callback function timed out.');
+ test.ok(err.code === 'ETIMEDOUT');
+ test.ok(results[0] === 'I didn\'t time out');
+ test.done();
+ });
+};
+
+exports['timeout with parallel'] = function (test) {
+ test.expect(3);
+
+ async.parallel([
+ async.timeout(function asyncFn(callback) {
+ setTimeout(function() {
+ callback(null, 'I didn\'t time out');
+ }, 50);
+ }, 200),
+ async.timeout(function asyncFn(callback) {
+ setTimeout(function() {
+ callback(null, 'I will time out');
+ }, 300);
+ }, 150)
+ ],
+ function(err, results) {
+ test.ok(err.message === 'Callback function timed out.');
+ test.ok(err.code === 'ETIMEDOUT');
+ test.ok(results[0] === 'I didn\'t time out');
+ test.done();
+ });
+};