Skip to content

Commit

Permalink
Merge 41ff549 into 9939951
Browse files Browse the repository at this point in the history
  • Loading branch information
edeustace committed Jun 16, 2015
2 parents 9939951 + 41ff549 commit 264564b
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 15 deletions.
15 changes: 8 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ This can also arise by accident if you callback early in certain cases:
```js
async.eachSeries(hugeArray, function iterator(item, callback) {
if (inCache(item)) {
callback(null, cache[item]); // if many items are cached, you'll overflow
callback(null, cache[item]); // if many items are cached, you'll overflow
} else {
doSomeIO(item, callback);
}
}, function done() {
//...
}, function done() {
//...
});
```

Expand Down Expand Up @@ -1459,7 +1459,7 @@ new tasks much easier (and the code more readable).
---------------------------------------
<a name="retry" />
### retry([times = 5], task, [callback])
### retry([times = 5], task, [callback, interval = 0])
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
Expand All @@ -1475,6 +1475,7 @@ __Arguments__
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`.
* `interval` - How long to wait in milliseconds before making another attempt. Defaults to 0 (aka don't wait).
The [`retry`](#retry) function can be used as a stand-alone control flow by passing a
callback, as shown below:
Expand Down Expand Up @@ -1646,7 +1647,7 @@ async.times(5, function(n, next){
<a name="timesSeries" />
### timesSeries(n, iterator, [callback])
The same as [`times`](#times), only the iterator is applied in series.
The same as [`times`](#times), only the iterator is applied in series.
The next `iterator` is only called once the current one has completed.
The results array will be in the same order as the original.
Expand Down Expand Up @@ -1727,9 +1728,9 @@ function sometimesAsync(arg, callback) {
}

// this has a risk of stack overflows if many results are cached in a row
async.mapSeries(args, sometimesAsync, done);
async.mapSeries(args, sometimesAsync, done);

// this will defer sometimesAsync's callback if necessary,
// this will defer sometimesAsync's callback if necessary,
// preventing stack overflows
async.mapSeries(args, async.ensureAsync(sometimesAsync), done);

Expand Down
62 changes: 54 additions & 8 deletions lib/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -601,17 +601,49 @@
});
};

async.retry = function(times, task, callback) {

async.retry = function(/*[times,] task [, callback, interval]*/) {
var DEFAULT_TIMES = 5;
var DEFAULT_INTERVAL = 0;

var attempts = [];
// Use defaults if times not passed
if (typeof times === 'function') {
callback = task;
task = times;
times = DEFAULT_TIMES;

var times = DEFAULT_TIMES;
var interval = DEFAULT_INTERVAL;
var task;
var callback;

switch(arguments.length){
case 1: {
task = arguments[0];
break;
}
case 2 : {
task = arguments[0];
callback = arguments[1];
break;
}
case 3: {
times = arguments[0];
task = arguments[1];
callback = arguments[2];
break;
}
case 4: {
times = arguments[0];
task = arguments[1];
callback = arguments[2];
interval = arguments[3];
break;
}
default: {
throw new Error('Invalid arguments - must be either (task), (task, callback), (times, task, callback) or (times, task, callback, interval)');
}
}
// Make sure times is a number

// Make sure times and interval are numbers
times = parseInt(times, 10) || DEFAULT_TIMES;
interval = parseInt(interval, 10) || DEFAULT_INTERVAL;

function wrappedTask(wrappedCallback, wrappedResults) {
function retryAttempt(task, finalAttempt) {
Expand All @@ -622,8 +654,22 @@
};
}

function retryInterval(interval){
return function(seriesCallback){
setTimeout(function(){
seriesCallback(null);
}, interval);
};
}

while (times) {
attempts.push(retryAttempt(task, !(times-=1)));

var finalAttempt = !(times-=1);
attempts.push(retryAttempt(task, finalAttempt));
if(!finalAttempt && interval > 0){
attempts.push(retryInterval(interval));
}

}
async.series(attempts, function(done, data){
data = data[data.length - 1];
Expand Down
22 changes: 22 additions & 0 deletions test/test-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,28 @@ exports['retry when all attempts succeeds'] = function(test) {
});
};

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, 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();
}, interval);
};

exports['retry as an embedded task'] = function(test) {
var retryResult = 'RETRY';
var fooResults;
Expand Down

0 comments on commit 264564b

Please sign in to comment.