From a211146f9c67057797fdce69446e1eb765616575 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Mon, 7 Mar 2016 23:46:01 -0800 Subject: [PATCH 1/4] added retryable wrapper for async tasks --- lib/index.js | 3 ++ lib/retryable.js | 11 +++++++ mocha_test/retryable.js | 65 +++++++++++++++++++++++++++++++++++++++++ test/test-async.js | 42 -------------------------- 4 files changed, 79 insertions(+), 42 deletions(-) create mode 100644 lib/retryable.js create mode 100644 mocha_test/retryable.js diff --git a/lib/index.js b/lib/index.js index 300491dda..869ba6e5b 100644 --- a/lib/index.js +++ b/lib/index.js @@ -49,6 +49,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'; @@ -115,6 +116,7 @@ export default { rejectLimit: rejectLimit, rejectSeries: rejectSeries, retry: retry, + retryable: retryable, seq: seq, series: series, setImmediate: setImmediate, @@ -199,6 +201,7 @@ export { rejectLimit as rejectLimit, rejectSeries as rejectSeries, retry as retry, + retryable as retryable, seq as seq, series as series, setImmediate as setImmediate, diff --git a/lib/retryable.js b/lib/retryable.js new file mode 100644 index 000000000..5bb3ccd99 --- /dev/null +++ b/lib/retryable.js @@ -0,0 +1,11 @@ +import retry from './retry'; +import rest from 'lodash/rest'; + +export default function (opts, task) { + return rest(function (args) { + var callback = args.pop(); + retry(opts, function (cb) { + task(...args.concat([cb])); + }, callback); + }); +} diff --git a/mocha_test/retryable.js b/mocha_test/retryable.js new file mode 100644 index 000000000..8cad4035f --- /dev/null +++ b/mocha_test/retryable.js @@ -0,0 +1,65 @@ +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); + }); + + // TODO: re-enable when auto's args are swapped + it.skip('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(); + }); + }); +}); diff --git a/test/test-async.js b/test/test-async.js index 2e041787f..1e5dcb162 100755 --- a/test/test-async.js +++ b/test/test-async.js @@ -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['waterfall'] = { From 1db668ee045c69df6511d6265a52ebfb84020e94 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Fri, 11 Mar 2016 20:20:24 -0800 Subject: [PATCH 2/4] remove splat --- lib/retryable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/retryable.js b/lib/retryable.js index 5bb3ccd99..3d73f79d8 100644 --- a/lib/retryable.js +++ b/lib/retryable.js @@ -5,7 +5,7 @@ export default function (opts, task) { return rest(function (args) { var callback = args.pop(); retry(opts, function (cb) { - task(...args.concat([cb])); + task.apply(null, args.concat([cb])); }, callback); }); } From ffa7b0fe282e6edd41c52d2120f44ce335ee7701 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Fri, 11 Mar 2016 20:25:01 -0800 Subject: [PATCH 3/4] re-enable test, handle omitted retry options --- lib/retryable.js | 13 +++++++++++-- mocha_test/retryable.js | 3 +-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/retryable.js b/lib/retryable.js index 3d73f79d8..26fa6099c 100644 --- a/lib/retryable.js +++ b/lib/retryable.js @@ -2,10 +2,19 @@ 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(); - retry(opts, function (cb) { + + function taskFn(cb) { task.apply(null, args.concat([cb])); - }, callback); + } + + if (opts) retry(opts, taskFn, callback); + else retry(taskFn, callback); + }); } diff --git a/mocha_test/retryable.js b/mocha_test/retryable.js index 8cad4035f..814162971 100644 --- a/mocha_test/retryable.js +++ b/mocha_test/retryable.js @@ -21,8 +21,7 @@ describe('retryable', function () { }, 15); }); - // TODO: re-enable when auto's args are swapped - it.skip('should work as an embedded task', function(done) { + it('should work as an embedded task', function(done) { var retryResult = 'RETRY'; var fooResults; var retryResults; From 7980923b12ee288243bc922525f70b07c3a41872 Mon Sep 17 00:00:00 2001 From: Alexander Early Date: Fri, 11 Mar 2016 21:40:30 -0800 Subject: [PATCH 4/4] add docs for retryable --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index 734e1aaab..041689965 100644 --- a/README.md +++ b/README.md @@ -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) @@ -1580,6 +1581,31 @@ async.auto({ }); ``` + +--------------------------------------- + + + +### 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) +``` + ---------------------------------------