retry vs promise-retry vs exponential-backoff vs async-retry vs backoff
Retry Mechanisms in JavaScript Comparison
1 Year
retrypromise-retryexponential-backoffasync-retrybackoffSimilar Packages:
What's Retry Mechanisms in JavaScript?

Retry mechanisms in JavaScript are techniques used to automatically attempt a failed operation multiple times before giving up. This is particularly useful for handling transient errors, such as network failures or temporary unavailability of a service. By implementing a retry strategy, developers can improve the resilience of their applications, reduce the likelihood of failures, and enhance the user experience. These mechanisms can be customized with parameters like the number of retries, delay between attempts, and backoff strategies to control how retries are performed. The following libraries provide various implementations of retry mechanisms in JavaScript, each with its own features and use cases.

Package Weekly Downloads Trend
Github Stars Ranking
Stat Detail
Package
Downloads
Stars
Size
Issues
Publish
License
retry42,330,9141,247-194 years agoMIT
promise-retry16,078,728317-115 years agoMIT
exponential-backoff12,458,96637055.2 kB62 months agoApache-2.0
async-retry9,697,5741,871-304 years agoMIT
backoff712,113337-119 years agoMIT
Feature Comparison: retry vs promise-retry vs exponential-backoff vs async-retry vs backoff

Backoff Strategies

  • retry:

    retry supports various backoff strategies, including exponential and linear, and allows for extensive customization of the backoff behavior through options.

  • promise-retry:

    promise-retry supports customizable delays between retries, but does not have built-in support for multiple backoff strategies. You can implement your own logic for backoff if needed.

  • exponential-backoff:

    exponential-backoff focuses solely on exponential backoff, providing a simple and efficient implementation for scenarios where this strategy is sufficient.

  • async-retry:

    async-retry allows you to define custom backoff strategies, including exponential backoff, but does not provide built-in support for multiple backoff types.

  • backoff:

    backoff provides a wide range of backoff strategies, including exponential, linear, and custom backoff, with the ability to easily switch between them and configure their behavior.

Error Handling

  • retry:

    retry allows for custom error handling and provides options to specify which errors should trigger a retry. You can implement logic to handle specific error types.

  • promise-retry:

    promise-retry allows you to handle errors and customize the retry logic based on the error. You can provide a function to determine whether to retry based on the error type.

  • exponential-backoff:

    exponential-backoff provides basic error handling, but it is up to the developer to implement custom logic for handling errors and determining when to stop retrying.

  • async-retry:

    async-retry allows you to handle errors and decide whether to retry based on the error type or message. You can implement custom logic to determine when to stop retrying.

  • backoff:

    backoff provides event hooks for error handling, allowing you to respond to errors, retries, and completion events. This makes it easy to integrate custom error handling logic.

Promise Support

  • retry:

    retry supports both callback and promise-based APIs, allowing for flexibility in how you implement retry logic in your code.

  • promise-retry:

    promise-retry is specifically designed for promise-based operations, making it easy to integrate into async functions and handle retries with promises.

  • exponential-backoff:

    exponential-backoff is designed for use with promises, making it suitable for retrying asynchronous operations that return promises.

  • async-retry:

    async-retry is designed for asynchronous operations and works seamlessly with promises. It supports both async/await and promise-based APIs.

  • backoff:

    backoff is primarily focused on backoff strategies and does not provide built-in support for promises. However, it can be integrated with promise-based code.

Customization

  • retry:

    retry provides extensive customization options for retries, delays, and backoff strategies. It is versatile and can be configured to handle a wide range of retry scenarios.

  • promise-retry:

    promise-retry allows for customization of the retry count, delay, and error handling. It is straightforward to use and configure, making it suitable for most use cases.

  • exponential-backoff:

    exponential-backoff is simple and lightweight, but it has limited customization options. It is best suited for scenarios where a straightforward exponential backoff implementation is needed.

  • async-retry:

    async-retry offers a high degree of customization, allowing you to define the number of retries, backoff strategy, and error handling logic. It is flexible and easy to configure.

  • backoff:

    backoff is highly customizable, allowing you to configure backoff strategies, error handling, and event listeners. It provides a lot of flexibility for complex use cases.

Ease of Use: Code Examples

  • retry:

    Using retry for a callback-based operation

    const retry = require('retry');
    
    const operation = retry.operation({
      retries: 5,
      factor: 2,
      minTimeout: 1000,
      maxTimeout: 5000,
    });
    
    operation.attempt((currentAttempt) => {
      console.log(`Attempt ${currentAttempt}`);
      // Simulate a failing operation
      const error = new Error('Operation failed');
      if (operation.retry(error)) {
        console.log(`Retrying...`);
      } else {
        console.error(`Failed after ${operation.attempts()} attempts`);
      }
    });
    
  • promise-retry:

    Retrying a promise-based function with promise-retry

    const promiseRetry = require('promise-retry');
    
    function unreliableApiCall(retry, attempt) {
      console.log(`Attempt ${attempt}`);
      return new Promise((resolve, reject) => {
        const success = Math.random() > 0.5; // 50% chance of failure
        setTimeout(() => (success ? resolve('Data') : reject(new Error('Failed'))), 1000);
      });
    }
    
    promiseRetry((retry) => {
      return unreliableApiCall(retry);
    }, {
      retries: 5,
      minTimeout: 1000,
      maxTimeout: 5000,
    }).then(console.log).catch(console.error);
    
  • exponential-backoff:

    Exponential backoff example

    const exponentialBackoff = require('exponential-backoff');
    
    function unreliableFunction() {
      return new Promise((resolve, reject) => {
        const success = Math.random() > 0.7; // 30% chance of failure
        setTimeout(() => (success ? resolve('Success!') : reject(new Error('Failed'))), 1000);
      });
    }
    
    const backoff = exponentialBackoff.backoff({
      initialDelay: 1000,
      maxDelay: 16000,
      multiplier: 2,
    });
    
    backoff.retry(unreliableFunction)
      .then(result => console.log(result))
      .catch(error => console.error(error));
    
  • async-retry:

    Retrying an asynchronous operation with async-retry

    const retry = require('async-retry');
    
    async function fetchData() {
      // Simulate a function that may fail
      throw new Error('Network error');
    }
    
    async function main() {
      try {
        const result = await retry(fetchData, {
          retries: 3,
          factor: 2,
          minTimeout: 1000,
          maxTimeout: 5000,
        });
        console.log('Data fetched:', result);
      } catch (error) {
        console.error('Failed to fetch data:', error);
      }
    }
    
    main();
    
  • backoff:

    Using backoff for exponential backoff

    const backoff = require('backoff');
    
    const operation = backoff.call(function (callback) {
      // Simulate a failing operation
      console.log('Attempting operation...');
      callback(new Error('Operation failed'));
    });
    
    operation.failAfter(5); // Fail after 5 attempts
    operation.on('backoff', function (number, delay) {
      console.log(`Backoff #${number} for ${delay}ms`);
    });
    operation.on('fail', function () {
      console.log('Operation failed after multiple attempts.');
    });
    operation.start();
    
How to Choose: retry vs promise-retry vs exponential-backoff vs async-retry vs backoff
  • retry:

    Choose retry if you need a versatile library that supports both callback and promise-based APIs for retrying operations, with extensive customization options for retries, delays, and backoff strategies.

  • promise-retry:

    Choose promise-retry if you want a straightforward way to retry promise-based operations with support for customizable retry counts and delays, while keeping the API simple and easy to use.

  • exponential-backoff:

    Choose exponential-backoff if you specifically need a lightweight implementation of exponential backoff for retrying operations, especially in scenarios like API calls or network requests.

  • async-retry:

    Choose async-retry if you need a simple and flexible solution for retrying asynchronous operations with customizable retry logic and backoff strategies.

  • backoff:

    Choose backoff if you require a comprehensive library that supports various backoff strategies, including exponential, linear, and custom backoff, with built-in event handling.

README for retry

Build Status codecov

retry

Abstraction for exponential and custom retry strategies for failed operations.

Installation

npm install retry

Current Status

This module has been tested and is ready to be used.

Tutorial

The example below will retry a potentially failing dns.resolve operation 10 times using an exponential backoff strategy. With the default settings, this means the last attempt is made after 17 minutes and 3 seconds.

var dns = require('dns');
var retry = require('retry');

function faultTolerantResolve(address, cb) {
  var operation = retry.operation();

  operation.attempt(function(currentAttempt) {
    dns.resolve(address, function(err, addresses) {
      if (operation.retry(err)) {
        return;
      }

      cb(err ? operation.mainError() : null, addresses);
    });
  });
}

faultTolerantResolve('nodejs.org', function(err, addresses) {
  console.log(err, addresses);
});

Of course you can also configure the factors that go into the exponential backoff. See the API documentation below for all available settings. currentAttempt is an int representing the number of attempts so far.

var operation = retry.operation({
  retries: 5,
  factor: 3,
  minTimeout: 1 * 1000,
  maxTimeout: 60 * 1000,
  randomize: true,
});

API

retry.operation([options])

Creates a new RetryOperation object. options is the same as retry.timeouts()'s options, with three additions:

  • forever: Whether to retry forever, defaults to false.
  • unref: Whether to unref the setTimeout's, defaults to false.
  • maxRetryTime: The maximum time (in milliseconds) that the retried operation is allowed to run. Default is Infinity.

retry.timeouts([options])

Returns an array of timeouts. All time options and return values are in milliseconds. If options is an array, a copy of that array is returned.

options is a JS object that can contain any of the following keys:

  • retries: The maximum amount of times to retry the operation. Default is 10. Seting this to 1 means do it once, then retry it once.
  • factor: The exponential factor to use. Default is 2.
  • minTimeout: The number of milliseconds before starting the first retry. Default is 1000.
  • maxTimeout: The maximum number of milliseconds between two retries. Default is Infinity.
  • randomize: Randomizes the timeouts by multiplying with a factor between 1 to 2. Default is false.

The formula used to calculate the individual timeouts is:

Math.min(random * minTimeout * Math.pow(factor, attempt), maxTimeout)

Have a look at this article for a better explanation of approach.

If you want to tune your factor / times settings to attempt the last retry after a certain amount of time, you can use wolfram alpha. For example in order to tune for 10 attempts in 5 minutes, you can use this equation:

screenshot

Explaining the various values from left to right:

  • k = 0 ... 9: The retries value (10)
  • 1000: The minTimeout value in ms (1000)
  • x^k: No need to change this, x will be your resulting factor
  • 5 * 60 * 1000: The desired total amount of time for retrying in ms (5 minutes)

To make this a little easier for you, use wolfram alpha to do the calculations:

http://www.wolframalpha.com/input/?i=Sum%5B1000*x^k%2C+{k%2C+0%2C+9}%5D+%3D+5+*+60+*+1000

retry.createTimeout(attempt, opts)

Returns a new timeout (integer in milliseconds) based on the given parameters.

attempt is an integer representing for which retry the timeout should be calculated. If your retry operation was executed 4 times you had one attempt and 3 retries. If you then want to calculate a new timeout, you should set attempt to 4 (attempts are zero-indexed).

opts can include factor, minTimeout, randomize (boolean) and maxTimeout. They are documented above.

retry.createTimeout() is used internally by retry.timeouts() and is public for you to be able to create your own timeouts for reinserting an item, see issue #13.

retry.wrap(obj, [options], [methodNames])

Wrap all functions of the obj with retry. Optionally you can pass operation options and an array of method names which need to be wrapped.

retry.wrap(obj)

retry.wrap(obj, ['method1', 'method2'])

retry.wrap(obj, {retries: 3})

retry.wrap(obj, {retries: 3}, ['method1', 'method2'])

The options object can take any options that the usual call to retry.operation can take.

new RetryOperation(timeouts, [options])

Creates a new RetryOperation where timeouts is an array where each value is a timeout given in milliseconds.

Available options:

  • forever: Whether to retry forever, defaults to false.
  • unref: Wether to unref the setTimeout's, defaults to false.

If forever is true, the following changes happen:

  • RetryOperation.errors() will only output an array of one item: the last error.
  • RetryOperation will repeatedly use the timeouts array. Once all of its timeouts have been used up, it restarts with the first timeout, then uses the second and so on.

retryOperation.errors()

Returns an array of all errors that have been passed to retryOperation.retry() so far. The returning array has the errors ordered chronologically based on when they were passed to retryOperation.retry(), which means the first passed error is at index zero and the last is at the last index.

retryOperation.mainError()

A reference to the error object that occured most frequently. Errors are compared using the error.message property.

If multiple error messages occured the same amount of time, the last error object with that message is returned.

If no errors occured so far, the value is null.

retryOperation.attempt(fn, timeoutOps)

Defines the function fn that is to be retried and executes it for the first time right away. The fn function can receive an optional currentAttempt callback that represents the number of attempts to execute fn so far.

Optionally defines timeoutOps which is an object having a property timeout in miliseconds and a property cb callback function. Whenever your retry operation takes longer than timeout to execute, the timeout callback function cb is called.

retryOperation.try(fn)

This is an alias for retryOperation.attempt(fn). This is deprecated. Please use retryOperation.attempt(fn) instead.

retryOperation.start(fn)

This is an alias for retryOperation.attempt(fn). This is deprecated. Please use retryOperation.attempt(fn) instead.

retryOperation.retry(error)

Returns false when no error value is given, or the maximum amount of retries has been reached.

Otherwise it returns true, and retries the operation after the timeout for the current attempt number.

retryOperation.stop()

Allows you to stop the operation being retried. Useful for aborting the operation on a fatal error etc.

retryOperation.reset()

Resets the internal state of the operation object, so that you can call attempt() again as if this was a new operation object.

retryOperation.attempts()

Returns an int representing the number of attempts it took to call fn before it was successful.

License

retry is licensed under the MIT license.

Changelog

0.10.0 Adding stop functionality, thanks to @maxnachlinger.

0.9.0 Adding unref functionality, thanks to @satazor.

0.8.0 Implementing retry.wrap.

0.7.0 Some bug fixes and made retry.createTimeout() public. Fixed issues #10, #12, and #13.

0.6.0 Introduced optional timeOps parameter for the attempt() function which is an object having a property timeout in milliseconds and a property cb callback function. Whenever your retry operation takes longer than timeout to execute, the timeout callback function cb is called.

0.5.0 Some minor refactoring.

0.4.0 Changed retryOperation.try() to retryOperation.attempt(). Deprecated the aliases start() and try() for it.

0.3.0 Added retryOperation.start() which is an alias for retryOperation.try().

0.2.0 Added attempts() function and parameter to retryOperation.try() representing the number of attempts it took to call fn().