diff options
author | Jesse Houchins <jesse.houchins@tstmedia.com> | 2014-03-28 22:37:01 -0500 |
---|---|---|
committer | Jesse Houchins <jesse.houchins@tstmedia.com> | 2014-03-28 22:37:01 -0500 |
commit | 04df2a7eca7e0ddaafe9b866753a6352ef567872 (patch) | |
tree | f36a45677b24edc604c4b3024ced9d2c68113a8c | |
parent | f1f9bfe9950b126da81ee3fe6bd16d891138ab87 (diff) | |
download | async-04df2a7eca7e0ddaafe9b866753a6352ef567872.tar.gz |
implement async.retry using async.series
-rw-r--r-- | README.md | 42 | ||||
-rwxr-xr-x | lib/async.js | 27 | ||||
-rwxr-xr-x | test/test-async.js | 35 |
3 files changed, 104 insertions, 0 deletions
@@ -146,6 +146,7 @@ Usage: * [`queue`](#queue) * [`cargo`](#cargo) * [`auto`](#auto) +* [`retry`](#retry) * [`iterator`](#iterator) * [`apply`](#apply) * [`nextTick`](#nextTick) @@ -1325,6 +1326,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 controll 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 controll 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 controll 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 42b2621..92c684a 100755 --- a/lib/async.js +++ b/lib/async.js @@ -481,6 +481,33 @@ }); }; + 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 retryAttempt = function(task, finalAttempt) { + return function(seriesCallback, results) { + task(function(err, result){ + seriesCallback(!err || finalAttempt, {err: err, result: result}); + }, results); + }; + }; + while (times) { + attempts.push(retryAttempt(task, !(times-=1))); + } + async.series(attempts, function(done, data){ + data = data[data.length - 1]; + callback(data.err, data.result); + }); + }; + async.waterfall = function (tasks, callback) { callback = callback || function () {}; if (!_isArray(tasks)) { diff --git a/test/test-async.js b/test/test-async.js index 32a6cba..7095af5 100755 --- a/test/test-async.js +++ b/test/test-async.js @@ -579,6 +579,41 @@ 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['waterfall'] = function(test){ test.expect(6); var call_order = []; |