summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJesse Houchins <jesse.houchins@tstmedia.com>2014-03-28 22:37:01 -0500
committerJesse Houchins <jesse.houchins@tstmedia.com>2014-03-28 22:37:01 -0500
commit04df2a7eca7e0ddaafe9b866753a6352ef567872 (patch)
treef36a45677b24edc604c4b3024ced9d2c68113a8c
parentf1f9bfe9950b126da81ee3fe6bd16d891138ab87 (diff)
downloadasync-04df2a7eca7e0ddaafe9b866753a6352ef567872.tar.gz
implement async.retry using async.series
-rw-r--r--README.md42
-rwxr-xr-xlib/async.js27
-rwxr-xr-xtest/test-async.js35
3 files changed, 104 insertions, 0 deletions
diff --git a/README.md b/README.md
index acee1b2..278c315 100644
--- a/README.md
+++ b/README.md
@@ -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 = [];