Skip to content

Instantly share code, notes, and snippets.

@andresmatasuarez
Last active February 29, 2016 02:08
Show Gist options
  • Save andresmatasuarez/f1a3251d9db4ff9d6fd6 to your computer and use it in GitHub Desktop.
Save andresmatasuarez/f1a3251d9db4ff9d6fd6 to your computer and use it in GitHub Desktop.
Node Bluebird Exponential Backoff
// Example usage
// The example will fetch current weather data from Buenos Aires,
// failing on purpose the first 2 times to demonstrate the Backoff strategy.
// Ensure you have Bluebird and superagent installed.
//
// Save the following code in example.js and run it with 'node example.js'
const Bluebird = require('bluebird');
const superagent = require('superagent');
const promiseUtils = require('./promise_utils');
const WEATHER_URL = 'http://api.openweathermap.org/data/2.5/weather?q=Buenos%20Aires&appid=44db6a862fba0b067b1930da0d769e98';
// Keep track of number of retries
var retries = 0;
promiseUtils.retry(() => {
console.log('Fetching data...');
return Bluebird.try(() => {
if (retries < 2) {
retries += 1;
throw new Error(`Failed number ${retries}`);
}
})
.then(() => Bluebird.promisify(superagent.get)(WEATHER_URL))
.then((res) => res.body.weather[0].description);
}, {
maxAttempts: 2,
shouldRetry: (err) => {
console.log('Error:', err.message);
console.log('Retrying...');
return true;
}
})
.then((weather) => console.log('Finished. Buenos Aires weather is: ', weather));
/**
* https://gist.github.com/andresmatasuarez/f1a3251d9db4ff9d6fd6
*
* @module PromiseUtils
*/
'use strict';
const _ = require('lodash');
const Bluebird = require('bluebird');
const Chance = require('chance');
const chance = new Chance();
/**
* @default
*/
const DEFAULT_SHOULD_RETRY = true;
/**
* @default
*/
const DEFAULT_MAX_ATTEMPTS = 5;
/**
* @callback shouldRetry
* @param {object} err - Error object.
* @returns {boolean} true if err should cause the action to retry. false, otherwise.
*/
/**
* retry
*
* @desc Returns a promise that gets resolved when 'action' is completed, or rejected if 'action' still fails after 'max' attempts using Exponential Backoff strategy.
*
* @param {function} action - Action to be performed. Must return a promise.
* @param {object} options - Configuration options
* @param {boolean|shouldRetry} [options.shouldRetry=DEFAULT_SHOULD_RETRY] - Condition that determines when a failing action should be retried or not.
* @param {number} [options.maxAttempts=DEFAULT_MAX_ATTEMPTS] - Max number of attempts before rejecting a failing action.
* @example
* const action = function(){
* return asyncAction(...);
* };
*
* retry(action).then(console.log);
*
* retry(action, {
* maxAttempts: 10,
* shouldRetry: (err) => {
* return err.code === 403;
* }
* }).then(console.log);
*/
exports.retry = function retry(action, options){
// Options
options = _.merge({
shouldRetry: DEFAULT_SHOULD_RETRY,
maxAttempts: DEFAULT_MAX_ATTEMPTS
}, options);
if (!_.isFunction(options.shouldRetry)){
options.shouldRetry = _.constant(!!options.shouldRetry);
}
function performAction(attempt, resolve, reject, err){
return action()
.then(resolve)
.catch((err) => {
if (!options.shouldRetry(err) || attempt >= options.maxAttempts){
return reject(err);
}
var delay = Math.pow(2, attempt) * 1000 + chance.millisecond();
return Bluebird.delay(delay)
.then(() => performAction(attempt + 1, resolve, reject, err));
});
}
return new Bluebird(function(resolve, reject){
performAction(0, resolve, reject);
});
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment