diff options
author | Caolan McMahon <caolan.mcmahon@gmail.com> | 2014-03-30 13:03:26 +0100 |
---|---|---|
committer | Caolan McMahon <caolan.mcmahon@gmail.com> | 2014-03-30 13:03:26 +0100 |
commit | 7f427ba1dd8707419c501169a3193a8b5a0126ae (patch) | |
tree | d56dfff65fd5e170f2f939019ead24fb0a714165 | |
parent | b7144bbc0322593b4d285a802dc6ef67f92f4d6a (diff) | |
parent | df06f5d166c4cf8930d9db2afd8410fadb1bd241 (diff) | |
download | async-7f427ba1dd8707419c501169a3193a8b5a0126ae.tar.gz |
Merge pull request #475 from jessehouchins/async.retry
Async.retry
-rw-r--r-- | README.md | 42 | ||||
-rwxr-xr-x | lib/async.js | 31 | ||||
-rwxr-xr-x | test/test-async.js | 56 |
3 files changed, 129 insertions, 0 deletions
@@ -146,6 +146,7 @@ Usage: * [`queue`](#queue) * [`cargo`](#cargo) * [`auto`](#auto) +* [`retry`](#retry) * [`iterator`](#iterator) * [`apply`](#apply) * [`nextTick`](#nextTick) @@ -1328,6 +1329,47 @@ new tasks much easier (and the code more readable). --------------------------------------- +<a name="retry" /> +### retry([times = 5], task, [callback]) + +Attempts to get a successful response from `task` no more than `times` times before +returning an error. If the task is successful, the `callback` will be passed the result +of the successfull task. If all attemps fail, the callback will be passed the error and +result (if any) of the final attempt. + +__Arguments__ + +* `task(callback, results)` - A function which receives two arguments: (1) a `callback(err, result)` + which must be called when finished, passing `err` (which can be `null`) and the `result` of + the function's execution, and (2) a `results` object, containing the results of + the previously executed functions (if nested inside another control flow). +* `callback(err, results)` - An optional callback which is called when the + task has succeeded, or after the final failed attempt. It receives the `err` and `result` arguments of the last attempt at completing the `task`. + +The [`retry`](#retry) function can be used as a stand-alone control flow by passing a +callback, as shown below: + +```js +async.retry(3, apiMethod, function(err, result) { + // do something with the result +}); +``` + +It can also be embeded within other control flow functions to retry individual methods +that are not as reliable, like this: + +```js +async.auto({ + users: api.getUsers.bind(api), + payments: async.retry(3, api.getPayments.bind(api)) +}, function(err, results) { + // do something with the results +}); +``` + + +--------------------------------------- + <a name="iterator" /> ### iterator(tasks) diff --git a/lib/async.js b/lib/async.js index e5e90fa..4d460b3 100755 --- a/lib/async.js +++ b/lib/async.js @@ -481,6 +481,37 @@ }); }; + async.retry = function(times, task, callback) { + var DEFAULT_TIMES = 5; + var attempts = []; + // Use defaults if times not passed + if (typeof times === 'function') { + callback = task; + task = times; + times = DEFAULT_TIMES; + } + // Make sure times is a number + times = parseInt(times, 10) || DEFAULT_TIMES; + var wrappedTask = function(wrappedCallback, wrappedResults) { + var retryAttempt = function(task, finalAttempt) { + return function(seriesCallback) { + task(function(err, result){ + seriesCallback(!err || finalAttempt, {err: err, result: result}); + }, wrappedResults); + }; + }; + while (times) { + attempts.push(retryAttempt(task, !(times-=1))); + } + async.series(attempts, function(done, data){ + data = data[data.length - 1]; + (wrappedCallback || callback)(data.err, data.result); + }); + } + // If a callback is passed, run this as a controll flow + return callback ? wrappedTask() : wrappedTask + }; + async.waterfall = function (tasks, callback) { callback = callback || function () {}; if (!_isArray(tasks)) { diff --git a/test/test-async.js b/test/test-async.js index d93180d..e7a8820 100755 --- a/test/test-async.js +++ b/test/test-async.js @@ -579,6 +579,62 @@ exports['auto modifying results causes final callback to run early'] = function( }); }; +// 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, results) { + callCount++ + failed-- + if (!failed) callback(null, expectedResult) + else callback(true) // respond with error + } + async.retry(fn, function(err, result){ + 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, results) { + 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 as an embedded task'] = function(test) { + var retryResult = 'RETRY'; + var fooResults; + var retryResults; + + async.auto({ + foo: function(callback, results){ + fooResults = results; + callback(null, 'FOO'); + }, + retry: async.retry(function(callback, results) { + retryResults = results; + callback(null, retryResult); + }) + }, function(err, results){ + test.equal(results.retry, retryResult, "Incorrect result was returned from retry function"); + test.equal(fooResults, retryResults, "Incorrect results were passed to retry function"); + test.done(); + }); +}; + exports['waterfall'] = function(test){ test.expect(6); var call_order = []; |