diff --git a/index.js b/index.js index 6f0624f..dfaae48 100644 --- a/index.js +++ b/index.js @@ -14,15 +14,15 @@ var RetryStrategies = require('./strategies'); var _ = require('lodash'); var DEFAULTS = { - maxAttempts: 5, // try 5 times - retryDelay: 5000, // wait for 5s before trying again - fullResponse: true, // resolve promise with the full response object - promiseFactory: defaultPromiseFactory // Function to use a different promise implementation library + maxAttempts: 5, // try 5 times + retryDelay: 5000, // wait for 5s before trying again + fullResponse: true, // resolve promise with the full response object + promiseFactory: defaultPromiseFactory // Function to use a different promise implementation library }; // Default promise factory which use bluebird function defaultPromiseFactory(resolver) { - return when.promise(resolver); + return when.promise(resolver); } /** @@ -34,193 +34,193 @@ function defaultPromiseFactory(resolver) { */ function makePromise(requestInstance, promiseFactoryFn) { - // Resolver function wich assigns the promise (resolve, reject) functions - // to the requestInstance - function Resolver(resolve, reject) { - this._resolve = resolve; - this._reject = reject; - } + // Resolver function wich assigns the promise (resolve, reject) functions + // to the requestInstance + function Resolver(resolve, reject) { + this._resolve = resolve; + this._reject = reject; + } - return promiseFactoryFn(Resolver.bind(requestInstance)); + return promiseFactoryFn(Resolver.bind(requestInstance)); } function Request(url, options, f, retryConfig) { - // ('url') - if (_.isString(url)) { - // ('url', f) - if (_.isFunction(options)) { - f = options; - } - - if (!_.isObject(options)) { - options = {}; - } - - // ('url', {object}) - options.url = url; + // ('url') + if(_.isString(url)){ + // ('url', f) + if(_.isFunction(options)){ + f = options; } - if (_.isObject(url)) { - if (_.isFunction(options)) { - f = options; - } - options = url; + if(!_.isObject(options)){ + options = {}; } - this.maxAttempts = retryConfig.maxAttempts; - this.retryDelay = retryConfig.retryDelay; - this.fullResponse = retryConfig.fullResponse; - this.attempts = 0; - - /** - * Option object - * @type {Object} - */ - this.options = options; - - /** - * Return true if the request should be retried - * @type {Function} (err, response) -> Boolean - */ - this.retryStrategy = _.isFunction(options.retryStrategy) ? options.retryStrategy : RetryStrategies.HTTPOrNetworkError; - - /** - * Return a number representing how long request-retry should wait before trying again the request - * @type {Boolean} (err, response, body) -> Number - */ - this.delayStrategy = _.isFunction(options.delayStrategy) ? options.delayStrategy : function () { - return this.retryDelay; - }; - - this._timeout = null; - this._req = null; - - this._callback = _.isFunction(f) ? _.once(f) : null; - - // create the promise only when no callback was provided - if (!this._callback) { - this._promise = makePromise(this, retryConfig.promiseFactory); - } + // ('url', {object}) + options.url = url; + } - this.reply = function requestRetryReply(err, response, body) { - if (this._callback) { - return this._callback(err, response, body); - } + if(_.isObject(url)){ + if(_.isFunction(options)){ + f = options; + } + options = url; + } + + this.maxAttempts = retryConfig.maxAttempts; + this.retryDelay = retryConfig.retryDelay; + this.fullResponse = retryConfig.fullResponse; + this.attempts = 0; + + /** + * Option object + * @type {Object} + */ + this.options = options; + + /** + * Return true if the request should be retried + * @type {Function} (err, response) -> Boolean + */ + this.retryStrategy = _.isFunction(options.retryStrategy) ? options.retryStrategy : RetryStrategies.HTTPOrNetworkError; + + /** + * Return a number representing how long request-retry should wait before trying again the request + * @type {Boolean} (err, response, body) -> Number + */ + this.delayStrategy = _.isFunction(options.delayStrategy) ? options.delayStrategy : function() { return this.retryDelay; }; + + this._timeout = null; + this._req = null; + + this._callback = _.isFunction(f) ? _.once(f) : null; + + // create the promise only when no callback was provided + if (!this._callback) { + this._promise = makePromise(this, retryConfig.promiseFactory); + } + + this.reply = function requestRetryReply(err, response, body) { + if (this._callback) { + return this._callback(err, response, body); + } - if (err) { - return this._reject(err); - } + if (err) { + return this._reject(err); + } - // resolve with the full response or just the body - response = this.fullResponse ? response : body; - this._resolve(response); - }; + // resolve with the full response or just the body + response = this.fullResponse ? response : body; + this._resolve(response); + }; } Request.request = request; Request.prototype._tryUntilFail = function () { - this.maxAttempts--; - this.attempts++; - - this._req = Request.request(this.options, function (err, response, body) { - if (response) { - response.attempts = this.attempts; - } - if (this.retryStrategy(err, response, body) && this.maxAttempts > 0) { - this._timeout = setTimeout(this._tryUntilFail.bind(this), this.delayStrategy.call(this, err, response, body)); - return; - } - - this.reply(err, response, body); - }.bind(this)); + this.maxAttempts--; + this.attempts++; + + this._req = Request.request(this.options, function (err, response, body) { + if (response) { + response.attempts = this.attempts; + } + if (this.retryStrategy(err, response, body) && this.maxAttempts > 0) { + this._timeout = setTimeout(this._tryUntilFail.bind(this), this.delayStrategy.call(this, err, response, body)); + return; + } + + this.reply(err, response, body); + }.bind(this)); }; Request.prototype.abort = function () { - if (this._req) { - this._req.abort(); - } - clearTimeout(this._timeout); - this.reply(new Error('Aborted')); + if (this._req) { + this._req.abort(); + } + clearTimeout(this._timeout); + this.reply(new Error('Aborted')); }; // expose request methods from RequestRetry -['end', 'on', 'emit', 'once', 'setMaxListeners', 'start', 'removeListener', 'pipe', 'write', 'auth', ].forEach(function (requestMethod) { - Request.prototype[requestMethod] = function exposedRequestMethod() { - return this._req[requestMethod].apply(this._req, arguments); - }; +['end', 'on', 'emit', 'once', 'setMaxListeners', 'start', 'removeListener', 'pipe', 'write', 'auth'].forEach(function (requestMethod) { + Request.prototype[requestMethod] = function exposedRequestMethod () { + return this._req[requestMethod].apply(this._req, arguments); + }; }); // expose promise methods ['then', 'catch', 'finally', 'fail', 'done'].forEach(function (promiseMethod) { - Request.prototype[promiseMethod] = function exposedPromiseMethod() { - if (this._callback) { - throw new Error('A callback was provided but waiting a promise, use only one pattern'); - } - return this._promise[promiseMethod].apply(this._promise, arguments); - }; + Request.prototype[promiseMethod] = function exposedPromiseMethod () { + if (this._callback) { + throw new Error('A callback was provided but waiting a promise, use only one pattern'); + } + return this._promise[promiseMethod].apply(this._promise, arguments); + }; }); function Factory(url, options, f) { - var retryConfig = _.chain(_.isObject(url) ? url : options || {}).defaults(DEFAULTS).pick(Object.keys(DEFAULTS)).value(); - var req = new Request(url, options, f, retryConfig); - req._tryUntilFail(); - return req; + var retryConfig = _.chain(_.isObject(url) ? url : options || {}).defaults(DEFAULTS).pick(Object.keys(DEFAULTS)).value(); + var req = new Request(url, options, f, retryConfig); + req._tryUntilFail(); + return req; } // adds a helper for HTTP method `verb` to object `obj` function makeHelper(obj, verb) { - obj[verb] = function helper(url, options, f) { - // ('url') - if (_.isString(url)) { - // ('url', f) - if (_.isFunction(options)) { - f = options; - } - - if (!_.isObject(options)) { - options = {}; - } - - // ('url', {object}) - options.url = url; - } - - if (_.isObject(url)) { - if (_.isFunction(options)) { - f = options; - } - options = url; - } - - options.method = verb.toUpperCase(); - return obj(options, f); - }; + obj[verb] = function helper(url, options, f) { + // ('url') + if(_.isString(url)){ + // ('url', f) + if(_.isFunction(options)){ + f = options; + } + + if(!_.isObject(options)){ + options = {}; + } + + // ('url', {object}) + options.url = url; + } + + if(_.isObject(url)){ + if(_.isFunction(options)){ + f = options; + } + options = url; + } + + options.method = verb.toUpperCase(); + return obj(options, f); + }; } function defaults(defaultOptions, defaultF) { - var factory = function (options, f) { - if (typeof options === "string") { - options = { - uri: options - }; - } - return Factory.apply(null, [extend(true, {}, defaultOptions, options), f || defaultF]); - }; - - factory.defaults = function (newDefaultOptions, newDefaultF) { - return defaults.apply(null, [extend(true, {}, defaultOptions, newDefaultOptions), newDefaultF || defaultF]); - }; - - factory.Request = Request; - factory.RetryStrategies = RetryStrategies; + var factory = function (options, f) { + if (typeof options === "string") { + options = { uri: options }; + } + return Factory.apply(null, [ extend(true, {}, defaultOptions, options), f || defaultF ]); + }; + + factory.defaults = function (newDefaultOptions, newDefaultF) { + return defaults.apply(null, [ extend(true, {}, defaultOptions, newDefaultOptions), newDefaultF || defaultF ]); + }; + + factory.Request = Request; + factory.RetryStrategies = RetryStrategies; ['get', 'head', 'post', 'put', 'patch', 'delete'].forEach(function (verb) { - makeHelper(factory, verb); - }); - factory.del = factory['delete']; + makeHelper(factory, verb); + }); + factory.del = factory['delete']; - return factory; + ['jar', 'cookie'].forEach(function (method) { + factory[method] = Factory.Request.request[method]; + }); + + return factory; } module.exports = Factory; @@ -231,10 +231,10 @@ Factory.RetryStrategies = RetryStrategies; // define .get/.post/... helpers ['get', 'head', 'post', 'put', 'patch', 'delete'].forEach(function (verb) { - makeHelper(Factory, verb); + makeHelper(Factory, verb); }); Factory.del = Factory['delete']; - ['jar', 'cookie'].forEach(function (method) { - Factory[method] = Factory.Request.request[method]; +['jar', 'cookie'].forEach(function (method) { + Factory[method] = Factory.Request.request[method]; }); \ No newline at end of file diff --git a/package.json b/package.json index 5f7b681..9eda32d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "requestretry", "description": "request-retry wrap nodejs request to retry http(s) requests in case of error", - "version": "1.12.0", + "version": "1.12.1", "author": { "name": "Francois-Guillaume Ribreau", "email": "npm@fgribreau.com", diff --git a/test/defaults.test.js b/test/defaults.test.js index afb6d54..c4c949f 100644 --- a/test/defaults.test.js +++ b/test/defaults.test.js @@ -4,88 +4,67 @@ var request = require('../'); var t = require('chai').assert; describe('Defaults', function () { - it('should set the default options', function (done) { - var r = request.defaults({ - json: true, - qs: { - d: "{index}" - } - }); - r('http://www.filltext.com/?rows=1', function (err, response, body) { - t.strictEqual(response.statusCode, 200); - t.isNumber(body[0].d); - done(); - }); + it('should set the default options', function (done) { + var r = request.defaults({ + json: true, + qs: { d: "{index}" } }); + r('http://www.filltext.com/?rows=1', function (err, response, body) { + t.strictEqual(response.statusCode, 200); + t.isNumber(body[0].d); + done(); + }); + }); - it('should set a default function', function (done) { - var r = request.defaults({}, function (err, response, body) { - t.strictEqual(response.statusCode, 200); - t.isNumber(body[0].d); - done(); - }); - r({ - url: 'http://www.filltext.com/?rows=1&d={index}', - json: true - }); + it('should set a default function', function (done) { + var r = request.defaults({}, function (err, response, body) { + t.strictEqual(response.statusCode, 200); + t.isNumber(body[0].d); + done(); }); + r({ url: 'http://www.filltext.com/?rows=1&d={index}', json: true }); + }); - it('should prefer options supplied to the call over default options', function (done) { - var r = request.defaults({ - json: true, - qs: { - d: "foo" - } - }); - r.get({ - url: 'http://www.filltext.com/?rows=1', - qs: { - d: "{index}" - } - }, function (err, response, body) { - t.strictEqual(response.statusCode, 200); - t.isNumber(body[0].d); - done(); - }); + it('should prefer options supplied to the call over default options', function (done) { + var r = request.defaults({ + json: true, + qs: { d: "foo" } + }); + r.get({ url: 'http://www.filltext.com/?rows=1', qs: { d: "{index}" } }, function (err, response, body) { + t.strictEqual(response.statusCode, 200); + t.isNumber(body[0].d); + done(); }); + }); - it('should allow nesting', function (done) { - var level0 = request.defaults({ - baseUrl: 'http://www.filltext.com' - }); - var level1 = level0.defaults({ - json: true - }); - var level2 = level1.defaults({ - qs: { - d: "{index}" - }, - fullResponse: false - }); - level2.get('/?rows=1').then(function (body) { - t.isNumber(body[0].d); - done(); - }); + it('should allow nesting', function (done) { + var level0 = request.defaults({ + baseUrl: 'http://www.filltext.com' + }); + var level1 = level0.defaults({ + json: true }); + var level2 = level1.defaults({ + qs: { d: "{index}" }, + fullResponse: false + }); + level2.get('/?rows=1').then(function (body) { + t.isNumber(body[0].d); + done(); + }); + }); - it('should perform "deep" defaulting', function (done) { - var r = request.defaults({ - json: true, - qs: { - d: "{index}" - } - }); - r({ - url: 'http://www.filltext.com/?rows=1', - qs: { - x: "test" - } - }, function (err, response, body) { - t.strictEqual(response.statusCode, 200); - t.isNumber(body[0].d); - t.strictEqual(body[0].x, "test"); - done(); - }); + it('should perform "deep" defaulting', function (done) { + var r = request.defaults({ + json: true, + qs: { d: "{index}" } + }); + r({ url: 'http://www.filltext.com/?rows=1', qs: { x: "test" } }, function (err, response, body) { + t.strictEqual(response.statusCode, 200); + t.isNumber(body[0].d); + t.strictEqual(body[0].x, "test"); + done(); }); + }); }); \ No newline at end of file diff --git a/test/helpers.js b/test/helpers.js index 265f0aa..4ece009 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,103 +1,97 @@ 'use strict'; var http = require('http'); -var request = require('../').defaults({ - json: true -}); +var request = require('../').defaults({ json: true }); var t = require('chai').assert; describe('Helpers', function () { - var server; - var url; + var server; + var url; - before(function (done) { - server = http.createServer(function (req, res) { - res.writeHead(200, { - 'Content-Type': 'application/json' - }); - res.write(JSON.stringify({ - method: req.method - })); - res.end(); - }); - server.listen(0, 'localhost', function () { - url = 'http://' + server.address().address + ':' + server.address().port; - done(); - }); + before(function (done) { + server = http.createServer(function (req, res) { + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.write(JSON.stringify({ method: req.method })); + res.end(); }); - - it('should provide .get()', function (done) { - request.get(url, function (err, resp, body) { - t.strictEqual(resp.statusCode, 200); - t.strictEqual(resp.request.method, 'GET'); - t.strictEqual(body.method, 'GET'); - done(); - }); + server.listen(0, 'localhost', function () { + url = 'http://' + server.address().address + ':' + server.address().port; + done(); }); + }); - it('should provide .head()', function (done) { - request.head(url, function (err, resp, body) { - t.strictEqual(resp.statusCode, 200); - t.strictEqual(resp.request.method, 'HEAD'); - t.strictEqual(body, undefined); - done(); - }); + it('should provide .get()', function (done) { + request.get(url, function (err, resp, body) { + t.strictEqual(resp.statusCode, 200); + t.strictEqual(resp.request.method, 'GET'); + t.strictEqual(body.method, 'GET'); + done(); }); + }); - it('should provide .post()', function (done) { - request.post(url, function (err, resp, body) { - t.strictEqual(resp.statusCode, 200); - t.strictEqual(resp.request.method, 'POST'); - t.strictEqual(body.method, 'POST'); - done(); - }); + it('should provide .head()', function (done) { + request.head(url, function (err, resp, body) { + t.strictEqual(resp.statusCode, 200); + t.strictEqual(resp.request.method, 'HEAD'); + t.strictEqual(body, undefined); + done(); }); + }); - it('should provide .put()', function (done) { - request.put(url, function (err, resp, body) { - t.strictEqual(resp.statusCode, 200); - t.strictEqual(resp.request.method, 'PUT'); - t.strictEqual(body.method, 'PUT'); - done(); - }); + it('should provide .post()', function (done) { + request.post(url, function (err, resp, body) { + t.strictEqual(resp.statusCode, 200); + t.strictEqual(resp.request.method, 'POST'); + t.strictEqual(body.method, 'POST'); + done(); }); + }); - it('should provide .patch()', function (done) { - request.patch(url, function (err, resp, body) { - t.strictEqual(resp.statusCode, 200); - t.strictEqual(resp.request.method, 'PATCH'); - t.strictEqual(body.method, 'PATCH'); - done(); - }); + it('should provide .put()', function (done) { + request.put(url, function (err, resp, body) { + t.strictEqual(resp.statusCode, 200); + t.strictEqual(resp.request.method, 'PUT'); + t.strictEqual(body.method, 'PUT'); + done(); }); + }); - it('should provide .delete()', function (done) { - request.delete(url, function (err, resp, body) { - t.strictEqual(resp.statusCode, 200); - t.strictEqual(resp.request.method, 'DELETE'); - t.strictEqual(body.method, 'DELETE'); - done(); - }); + it('should provide .patch()', function (done) { + request.patch(url, function (err, resp, body) { + t.strictEqual(resp.statusCode, 200); + t.strictEqual(resp.request.method, 'PATCH'); + t.strictEqual(body.method, 'PATCH'); + done(); }); + }); - it('should provide .del()', function (done) { - request.del(url, function (err, resp, body) { - t.strictEqual(resp.statusCode, 200); - t.strictEqual(resp.request.method, 'DELETE'); - t.strictEqual(body.method, 'DELETE'); - done(); - }); + it('should provide .delete()', function (done) { + request.delete(url, function (err, resp, body) { + t.strictEqual(resp.statusCode, 200); + t.strictEqual(resp.request.method, 'DELETE'); + t.strictEqual(body.method, 'DELETE'); + done(); }); + }); - after(function (done) { - if (server) { - server.close(function () { - done(); - }); - } else { - done(); - } + it('should provide .del()', function (done) { + request.del(url, function (err, resp, body) { + t.strictEqual(resp.statusCode, 200); + t.strictEqual(resp.request.method, 'DELETE'); + t.strictEqual(body.method, 'DELETE'); + done(); }); + }); + + after(function (done) { + if (server) { + server.close(function () { + done(); + }); + } else { + done(); + } + }); }); \ No newline at end of file