@retryable/request v0.0.2
When the connection fails with one of ECONNRESET
, ENOTFOUND
, ESOCKETTIMEDOUT
, ETIMEDOUT
, ECONNREFUSED
, EHOSTUNREACH
, EPIPE
, EAI_AGAIN
or when an HTTP 5xx or 429 error occurrs, the request will automatically be re-attempted as these are often recoverable errors and will go away on retry.
Installation
Install with npm.
npm install --save @retryable/request
Usage
@retryable/request is a drop-in replacement for request but adds two new options maxAttempts
and retryDelay
. It also adds one property to the response (or the error object, upon a network error), attempts
. It supports callbacks or promises.
With callbacks
var request = require('@retryable/request');
request({
url: 'https://api.domain.com/v1/a/b',
json: true,
// The below parameters are specific to @retryable/request
maxAttempts: 5, // (default) try 5 times
retryDelay: 5000, // (default) wait for 5s before trying again
retryStrategy: request.RetryStrategies.HTTPOrNetworkError // (default) retry on 5xx or network errors
}, function(err, response, body){
// this callback will only be called when the request succeeded or after maxAttempts or on error
if (response) {
console.log('The number of request attempts: ' + response.attempts);
}
});
How to define your own retry strategy
A retry strategy let you specify when @retryable/request should retry a request
/**
* @param {Null | Object} err
* @param {Object} response
* @param {Object} body
* @param {Object} options copy
* @return {Boolean} true if the request should be retried
*/
function myRetryStrategy(err, response, body, options){
// retry the request if we had an error or if the response was a 'Bad Gateway'
return !!err || response.statusCode === 502;
}
/**
* @param {Null | Object} err
* @param {Object} response
* @param {Object} body
* @param {Object} options copy
* @return {Object} mustRetry: {Boolean} true if the request should be retried
* options: {Object} new options for request
*/
function myRetryStrategy(err, response, body, options){
options.url = 'new url'; //you can overwrite some attributes or create new object
return {
mustRetry: !!err || response.statusCode === 502,
options: options, //then it should be passed back, it will be used for new requests
}
}
/**
* With an asynchronous retry strategy
* @param {Null | Object} err
* @param {Object} response
* @param {Object} body
* @param {Object} options copy
* @return {Object} mustRetry: {Boolean} true if the request should be retried
* options: {Object} new options for request
*/
async function myRetryStrategy(err, response, body, options){
let token = await getNewApiAuthToken();
options.headers = {'Authorization': `Bearer ${token}`}
return {
mustRetry: true,
options: options, // retry with new auth token
}
}
request({
url: 'https://api.domain.com/v1/a/b'
json:true,
retryStrategy: myRetryStrategy
}, function(err, response, body){
// this callback will only be called when the request succeeded or after maxAttempts or on error
});
How to define your own delay strategy
A delay strategy let you specify how long @retryable/request should wait before trying again the request
/**
* @param {Null | Object} err
* @param {Object} response
* @param {Object} body
* @return {Number} number of milliseconds to wait before trying again the request
*/
function myDelayStrategy(err, response, body){
// set delay of retry to a random number between 500 and 3500 ms
return Math.floor(Math.random() * (3500 - 500 + 1) + 500);
}
request({
url: 'https://api.domain.com/v1/a/b'
json:true,
delayStrategy: myDelayStrategy // delayStrategy is called 1 less times than the maxAttempts set
}, function(err, response, body){
// this callback will only be called when the request succeeded or after maxAttempts or on error
});
Here is how to implement an exponential backoff strategy:
/**
* @param {Number} attempts The number of times that the request has been attempted.
* @return {Number} number of milliseconds to wait before retrying again the request.
*/
function getExponentialBackoff(attempts) {
return (Math.pow(2, attempts) * 100) + Math.floor(Math.random() * 50);
}
function constructExponentialBackoffStrategy() {
let attempts = 0;
return () => {
attempts += 1;
return getExponentialBackoff(attempts);
};
}
request({
url: 'https://api.domain.com/v1/a/b'
json:true,
delayStrategy: constructExponentialBackoffStrategy() // need to invoke the function to return the closure.
}, function(err, response, body){
// this callback will only be called when the request succeeded or after maxAttempts or on error
});
How to access the underlying request library
You can access to the underlying request
library thanks to request.Request
:
const request = require('@retryable/request');
console.log(request.Request); // original request library
Thus, if needed, it's possible to monkey-patch or extend the underlying Request library:
request.Request = class extends request.Request {
constructor(url, options, f, retryConfig) {
super(url, options, f, retryConfig);
// this constructor will be called for every @retryable/request call,
// and give you global logging
console.log('Request', url, options, f, retryConfig);
}
}
Modifying request
options
You can use the defaults
method to provide default options like so:
var request = require('@retryable/request').defaults({ json: true, retryStrategy: myRetryStrategy });
API surface
As with request
, several helpers are provided for various HTTP methods and usage:
request(options [, callback])
.request(url [, callback])
- same asrequest(options [, callback])
.request(url, options [, callback])
- same asrequest(options [, callback])
.request.get(url [, callback])
- same asrequest(options [, callback])
, defaultsoptions.method
toGET
.request.get(url, options [, callback])
- same asrequest(options [, callback])
, defaultsoptions.method
toGET
.request.head(url)
- same asrequest(options [, callback])
, defaultsoptions.method
toHEAD
.request.post(url)
- same asrequest(options [, callback])
, defaultsoptions.method
toPOST
.request.put(url)
- same asrequest(options [, callback])
, defaultsoptions.method
toPUT
.request.patch(url)
- same asrequest(options [, callback])
, defaultsoptions.method
toPATCH
.request.del(url)
- same asrequest(options [, callback])
, defaultsoptions.method
toDELETE
.request.delete(url)
- same asrequest(options [, callback])
, defaultsoptions.method
toDELETE
.