Abstraction for exponential and custom retry strategies for failed operations.
npx @tessl/cli install tessl/npm-retry@0.13.0Retry provides abstraction for exponential and custom retry strategies for failed operations. It offers both operation-based retry patterns through RetryOperation objects and functional wrapper patterns that can automatically add retry logic to existing functions.
npm install retryconst retry = require("retry");Note: This package uses CommonJS exports only. ES module syntax can be used with bundlers or transpilers:
import retry from "retry";Individual function imports:
const { operation, timeouts, createTimeout, wrap } = require("retry");const retry = require("retry");
function faultTolerantResolve(address, cb) {
const operation = retry.operation();
operation.attempt(function(currentAttempt) {
dns.resolve(address, function(err, addresses) {
if (operation.retry(err)) {
return;
}
cb(err ? operation.mainError() : null, addresses);
});
});
}Retry is built around several key components:
Create and manage retry operations with exponential backoff strategies. The core functionality for handling failed operations with configurable retry policies.
/**
* Creates a new RetryOperation instance with computed timeouts
* @param {Object} [options] - Configuration options for retry behavior
* @param {number} [options.retries=10] - Maximum number of retry attempts
* @param {number} [options.factor=2] - Exponential backoff factor
* @param {number} [options.minTimeout=1000] - Minimum timeout in milliseconds
* @param {number} [options.maxTimeout=Infinity] - Maximum timeout in milliseconds
* @param {boolean} [options.randomize=false] - Add randomization to timeouts
* @param {boolean} [options.forever=false] - Whether to retry forever
* @param {boolean} [options.unref=false] - Whether to unref setTimeout calls
* @param {number} [options.maxRetryTime=Infinity] - Maximum time allowed for retries in milliseconds
* @returns {RetryOperation} RetryOperation instance
*/
function operation(options);Usage Examples:
const retry = require("retry");
// Basic retry with default settings (10 retries, exponential backoff)
const operation = retry.operation();
// Custom retry configuration
const operation = retry.operation({
retries: 5,
factor: 3,
minTimeout: 1 * 1000,
maxTimeout: 60 * 1000,
randomize: true,
});
// Retry forever with maximum retry time limit
const operation = retry.operation({
forever: true,
maxRetryTime: 5 * 60 * 1000, // 5 minutes max
});Generate arrays of timeout values for exponential backoff strategies. Used internally by operations but also available for custom timeout management.
/**
* Generates array of timeout values for exponential backoff
* @param {Object|Array} [options] - Timeout configuration or array of timeout values
* @param {number} [options.retries=10] - Maximum number of retries
* @param {number} [options.factor=2] - Exponential backoff factor
* @param {number} [options.minTimeout=1000] - Minimum timeout in milliseconds
* @param {number} [options.maxTimeout=Infinity] - Maximum timeout in milliseconds
* @param {boolean} [options.randomize=false] - Add randomization to timeouts
* @param {boolean} [options.forever=false] - Enable infinite retries
* @returns {number[]} Array of timeout values in milliseconds
*/
function timeouts(options);
/**
* Calculates timeout value for specific attempt number
* @param {number} attempt - Zero-indexed attempt number
* @param {Object} opts - Options containing factor, minTimeout, maxTimeout, randomize
* @param {number} opts.factor - Exponential backoff factor
* @param {number} opts.minTimeout - Minimum timeout in milliseconds
* @param {number} opts.maxTimeout - Maximum timeout in milliseconds
* @param {boolean} [opts.randomize=false] - Add randomization to timeout
* @returns {number} Timeout value in milliseconds
*/
function createTimeout(attempt, opts);Usage Examples:
const retry = require("retry");
// Generate timeout array
const timeouts = retry.timeouts({
retries: 3,
factor: 2,
minTimeout: 1000,
maxTimeout: 5000,
randomize: true
});
// Result: [1000-2000, 2000-4000, 4000-5000] (randomized)
// Calculate specific timeout
const timeout = retry.createTimeout(2, {
factor: 2,
minTimeout: 1000,
maxTimeout: 10000
});
// Result: 4000 (1000 * 2^2)
// Use array of custom timeouts
const customTimeouts = retry.timeouts([100, 500, 1000, 2000]);Automatically add retry logic to existing object methods. Useful for wrapping APIs, database clients, or any object with methods that may fail.
/**
* Wraps object methods with retry logic
* @param {Object} obj - Object containing methods to wrap
* @param {Object|Array} [options] - Retry options or method names array
* @param {Array} [methods] - Array of method names to wrap
* @returns {void} Modifies obj in place
*/
function wrap(obj, options, methods);Usage Examples:
const retry = require("retry");
const dns = require("dns");
// Wrap all functions in an object
retry.wrap(dns);
// Wrap specific methods
retry.wrap(dns, ["resolve", "lookup"]);
// Wrap with custom retry options
retry.wrap(dns, {
retries: 3,
factor: 2,
minTimeout: 1000
});
// Wrap specific methods with options
retry.wrap(dns, {
retries: 5,
maxTimeout: 10000
}, ["resolve"]);
// The wrapped methods now automatically retry on errors
dns.resolve("example.com", function(err, addresses) {
// This will retry up to the configured number of times
console.log(err, addresses);
});Direct management of RetryOperation instances for fine-grained control over retry behavior and error handling.
/**
* RetryOperation constructor - manages individual retry operation lifecycle
* @param {number[]} timeouts - Array of timeout values in milliseconds
* @param {Object|boolean} [options] - Configuration options
* @param {boolean} [options.forever=false] - Whether to retry forever
* @param {boolean} [options.unref=false] - Whether to unref setTimeout calls
* @param {number} [options.maxRetryTime=Infinity] - Maximum time allowed for retries
*/
function RetryOperation(timeouts, options);
/**
* Executes function with retry capability
* @param {Function} fn - Function to execute, receives currentAttempt as parameter
* @param {Object} [timeoutOps] - Timeout configuration
* @param {number} [timeoutOps.timeout] - Operation timeout in milliseconds
* @param {Function} [timeoutOps.cb] - Timeout callback function
* @returns {void}
*/
RetryOperation.prototype.attempt = function(fn, timeoutOps);
/**
* Determines if operation should be retried based on error
* @param {Error|null} err - Error from operation attempt
* @returns {boolean} true if operation will be retried, false otherwise
*/
RetryOperation.prototype.retry = function(err);
/**
* Stops retry operation and clears timers
* @returns {void}
*/
RetryOperation.prototype.stop = function();
/**
* Resets operation to initial state for reuse
* @returns {void}
*/
RetryOperation.prototype.reset = function();
/**
* Returns array of all errors encountered during retries
* @returns {Error[]} Array of Error objects in chronological order
*/
RetryOperation.prototype.errors = function();
/**
* Returns current attempt count
* @returns {number} Number representing attempt count
*/
RetryOperation.prototype.attempts = function();
/**
* Returns most frequently occurring error
* @returns {Error|null} Error object or null if no errors
*/
RetryOperation.prototype.mainError = function();
/**
* @deprecated Use attempt() instead
* Deprecated alias for attempt()
* @param {Function} fn - Function to execute
* @returns {void}
*/
RetryOperation.prototype.try = function(fn);
/**
* @deprecated Use attempt() instead
* Deprecated alias for attempt()
* @param {Function} fn - Function to execute
* @returns {void}
*/
RetryOperation.prototype.start = function(fn);Usage Examples:
const retry = require("retry");
// Direct RetryOperation usage
const timeouts = retry.timeouts({ retries: 3 });
const operation = new retry.RetryOperation(timeouts);
operation.attempt(function(currentAttempt) {
console.log(`Attempt ${currentAttempt}`);
asyncOperation(function(err, result) {
if (err && err.message === 'FATAL_ERROR') {
// Stop retrying on fatal errors
operation.stop();
return callback(err);
}
if (operation.retry(err)) {
return; // Will retry
}
// Final result (success or max retries reached)
if (err) {
console.log('All errors:', operation.errors());
console.log('Main error:', operation.mainError());
console.log('Total attempts:', operation.attempts());
}
callback(operation.mainError(), result);
});
});
// Reset and reuse operation
operation.reset();
operation.attempt(/* ... */);/**
* Configuration options for retry operations
* @typedef {Object} RetryOptions
* @property {number} [retries=10] - Maximum number of retry attempts
* @property {number} [factor=2] - Exponential backoff factor
* @property {number} [minTimeout=1000] - Minimum timeout in milliseconds
* @property {number} [maxTimeout=Infinity] - Maximum timeout in milliseconds
* @property {boolean} [randomize=false] - Add randomization to timeouts
* @property {boolean} [forever=false] - Whether to retry forever
* @property {boolean} [unref=false] - Whether to unref setTimeout calls
* @property {number} [maxRetryTime=Infinity] - Maximum time allowed for retries in milliseconds
*/
/**
* Timeout operation configuration
* @typedef {Object} TimeoutOps
* @property {number} timeout - Operation timeout in milliseconds
* @property {Function} cb - Timeout callback function
*/The retry library provides comprehensive error handling and analysis:
errors()mainError() returns the most frequently occurring error based on error messagemaxRetryTime is exceededoperation.stop() to abort retries for unrecoverable errorsforever: true, only keeps the last error to prevent memory leaksThe timeout calculation uses the following formula:
timeout = Math.min(random * Math.max(minTimeout, 1) * Math.pow(factor, attempt), maxTimeout)Where random is 1 or a value between 1-2 if randomize is enabled. The Math.max(minTimeout, 1) ensures a minimum timeout of at least 1 millisecond.
const dns = require("dns");
const retry = require("retry");
function faultTolerantResolve(address, cb) {
const operation = retry.operation({
retries: 2,
factor: 2,
minTimeout: 1 * 1000,
maxTimeout: 2 * 1000,
randomize: true
});
operation.attempt(function(currentAttempt) {
dns.resolve(address, function(err, addresses) {
if (operation.retry(err)) {
return;
}
cb(operation.mainError(), operation.errors(), addresses);
});
});
}function apiCall(endpoint, data, callback) {
const operation = retry.operation({ retries: 3 });
operation.attempt(function(currentAttempt) {
httpRequest(endpoint, data, function(err, response) {
if (err && err.statusCode === 401) {
// Don't retry authentication errors
operation.stop();
return callback(err);
}
if (operation.retry(err)) {
return;
}
callback(operation.mainError(), response);
});
});
}const retry = require("retry");
// Wrap database client methods
retry.wrap(dbClient, {
retries: 5,
factor: 1.5,
minTimeout: 500,
maxTimeout: 10000
}, ["query", "connect"]);
// Now database operations automatically retry
dbClient.query("SELECT * FROM users", function(err, results) {
// This query will retry up to 5 times on failure
console.log(err, results);
});