Skip to content

Commit

Permalink
Merge pull request #1058 from caolan/retryable
Browse files Browse the repository at this point in the history
added retryable wrapper for async tasks
  • Loading branch information
aearly committed Mar 18, 2016
2 parents 902eec8 + 7980923 commit bdf4a96
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 42 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ Some functions are also available in the following forms:
* [`auto`](#auto)
* [`autoInject`](#autoInject)
* [`retry`](#retry)
* [`retryable`](#retryable)
* [`iterator`](#iterator)
* [`times`](#times), `timesSeries`, `timesLimit`
* [`race`](#race)
Expand Down Expand Up @@ -1580,6 +1581,31 @@ async.auto({
});
```
---------------------------------------
<a name="retryable"></a>
### retryable([opts = {times: 5, interval: 0}| 5], task)
A close relative of `retry`. This method wraps a task and makes it retryable, rather than immediately calling it with retries.
__Arguments__
* `opts` - optional options, exactly the same as from `retry`
* `task` - the asynchronous function to wrap
__Example__
```js
async.auto({
dep1: async.retryable(3, getFromFlakyService),
process: ["dep1", async.retryable(3, function (results, cb) {
maybeProcessData(results.dep1, cb)
})]
}, callback)
```
---------------------------------------
<a name="iterator"></a>
Expand Down
3 changes: 3 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ import reject from './reject';
import rejectLimit from './rejectLimit';
import rejectSeries from './rejectSeries';
import retry from './retry';
import retryable from './retryable';
import seq from './seq';
import series from './series';
import setImmediate from './setImmediate';
Expand Down Expand Up @@ -120,6 +121,7 @@ export default {
rejectLimit: rejectLimit,
rejectSeries: rejectSeries,
retry: retry,
retryable: retryable,
seq: seq,
series: series,
setImmediate: setImmediate,
Expand Down Expand Up @@ -207,6 +209,7 @@ export {
rejectLimit as rejectLimit,
rejectSeries as rejectSeries,
retry as retry,
retryable as retryable,
seq as seq,
series as series,
setImmediate as setImmediate,
Expand Down
20 changes: 20 additions & 0 deletions lib/retryable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import retry from './retry';
import rest from 'lodash/rest';

export default function (opts, task) {
if (!task) {
task = opts;
opts = null;
}
return rest(function (args) {
var callback = args.pop();

function taskFn(cb) {
task.apply(null, args.concat([cb]));
}

if (opts) retry(opts, taskFn, callback);
else retry(taskFn, callback);

});
}
64 changes: 64 additions & 0 deletions mocha_test/retryable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
var async = require('../lib');
var expect = require('chai').expect;
var assert = require('assert');

describe('retryable', function () {
it('basics', function (done) {
var calls = 0;
var retryableTask = async.retryable(3, function (arg, cb) {
calls++;
expect(arg).to.equal(42);
cb('fail');
});

retryableTask(42, function (err) {
expect(err).to.equal('fail');
expect(calls).to.equal(3);
done();
});

setTimeout(function () {
}, 15);
});

it('should work as an embedded task', function(done) {
var retryResult = 'RETRY';
var fooResults;
var retryResults;

async.auto({
dep: async.constant('dep'),
foo: ['dep', function(results, callback){
fooResults = results;
callback(null, 'FOO');
}],
retry: ['dep', async.retryable(function(results, callback) {
retryResults = results;
callback(null, retryResult);
})]
}, function(err, results){
assert.equal(results.retry, retryResult, "Incorrect result was returned from retry function");
assert.equal(fooResults, retryResults, "Incorrect results were passed to retry function");
done();
});
});

it('should work as an embedded task with interval', function(done) {
var start = new Date().getTime();
var opts = {times: 5, interval: 100};

async.auto({
foo: function(callback){
callback(null, 'FOO');
},
retry: async.retryable(opts, function(callback) {
callback('err');
})
}, function(){
var duration = new Date().getTime() - start;
var expectedMinimumDuration = (opts.times -1) * opts.interval;
assert(duration >= expectedMinimumDuration, "The duration should have been greater than " + expectedMinimumDuration + ", but was " + duration);
done();
});
});
});
42 changes: 0 additions & 42 deletions test/test-async.js
Original file line number Diff line number Diff line change
Expand Up @@ -278,48 +278,6 @@ exports['seq without callback'] = function (test) {
add2mul3.call(testcontext, 3);
};

// need to fix retry, this isn't working
/*
exports['retry as an embedded task'] = function(test) {
var retryResult = 'RETRY';
var fooResults;
var retryResults;
async.auto({
dep: async.constant('dep'),
foo: ['dep', function(results, callback){
fooResults = results;
callback(null, 'FOO');
}],
retry: ['dep', async.retry(function(results, callback) {
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['retry as an embedded task with interval'] = function(test) {
var start = new Date().getTime();
var opts = {times: 5, interval: 100};
async.auto({
foo: function(callback){
callback(null, 'FOO');
},
retry: async.retry(opts, function(callback) {
callback('err');
})
}, function(){
var duration = new Date().getTime() - start;
var expectedMinimumDuration = (opts.times -1) * opts.interval;
test.ok(duration >= expectedMinimumDuration, "The duration should have been greater than " + expectedMinimumDuration + ", but was " + duration);
test.done();
});
};*/

exports['parallel'] = function(test){
var call_order = [];
Expand Down

0 comments on commit bdf4a96

Please sign in to comment.