Exponential Backoff Retry Utility
The `exponential-backoff` package provides a robust utility for retrying asynchronous (Promise-returning) functions with an exponential delay between attempts. Currently stable at version 3.1.3, the library maintains an active development status with regular chore and security updates, although it does not adhere to a strict release cadence. Its key differentiators include extensive configurability through the `BackOffOptions` object, allowing control over aspects like initial delay, maximum delay, number of attempts, jitter application (`full` or `none`), and a custom `retry` function for conditional reattempts. The package ships with TypeScript types, enhancing developer experience in TypeScript projects. It is designed to be a flexible solution for handling transient errors in network requests, database operations, or other unreliable processes.
Common errors
-
TypeError: (0 , exponential_backoff__WEBPACK_IMPORTED_MODULE_0__.backOff) is not a function
cause This error typically occurs in environments (like Webpack or Babel) when trying to `require` an ESM-first package or due to incorrect default/named import resolution. It indicates the bundler couldn't find the `backOff` export.fixEnsure you are using standard ESM `import { backOff } from 'exponential-backoff';`. If using CommonJS, check your transpilation settings to ensure ESM imports are correctly handled or consider updating your build tools. In some cases, a package might not correctly expose its exports for CommonJS. -
My function keeps retrying indefinitely even after many failures.
cause You likely have `numOfAttempts` set to a very high number (or effectively `Infinity` due to an oversight) and are using the default `retry` function, which always returns `true`.fixConfigure `numOfAttempts` to a finite, reasonable number. Alternatively, provide a custom `retry` function that checks the error (`e`) or `attemptNumber` and returns `false` to stop retrying when a certain condition is met (e.g., non-transient error, max attempts reached). -
Argument of type '(...)' is not assignable to parameter of type 'BackOffOptions'. Object literal may only specify known properties, and '' does not exist in type 'BackOffOptions'.
cause This TypeScript error indicates you're passing an option to `backOff` that is not part of the `BackOffOptions` interface, likely due to a typo or using a deprecated option name.fixReview the `BackOffOptions` interface and the available options in the documentation. Correct any misspelled option names or remove unsupported properties from the options object.
Warnings
- breaking Major versions (e.g., v2 to v3) often introduce breaking changes to the API, options, or internal behavior. Always consult the official migration guide when upgrading between major versions.
- gotcha The default `retry` function always returns `true`. If `numOfAttempts` is set to `Infinity` (which is the default `maxDelay` but `numOfAttempts` defaults to 10), and your `request` function consistently fails, it can lead to an infinite retry loop.
- gotcha The `numOfAttempts` option has a minimum value of `1`. Providing a value less than `1` will result in the option being clamped to `1`, which might lead to unexpected behavior if you intend zero retries.
Install
-
npm install exponential-backoff -
yarn add exponential-backoff -
pnpm add exponential-backoff
Imports
- backOff
const { backOff } = require('exponential-backoff');import { backOff } from 'exponential-backoff'; - BackOffOptions
import { BackOffOptions } from 'exponential-backoff';import type { BackOffOptions } from 'exponential-backoff'; - JitterType
import type { JitterType } from 'exponential-backoff';
Quickstart
import { backOff } from 'exponential-backoff';
interface WeatherResponse {
temperature: number;
unit: string;
}
// Simulate an unreliable API call
let attemptCount = 0;
function getUnreliableWeather(): Promise<WeatherResponse> {
attemptCount++;
console.log(`Attempting to fetch weather... (Attempt ${attemptCount})`);
return new Promise((resolve, reject) => {
if (Math.random() > 0.7 || attemptCount >= 3) { // Succeeds on ~30% chance or after 3 attempts
resolve({ temperature: 25, unit: 'C' });
} else {
reject(new Error('Failed to fetch weather data.'));
}
});
}
async function main() {
try {
console.log('Starting weather fetch with exponential backoff...');
const response = await backOff(
() => getUnreliableWeather(),
{
numOfAttempts: 5, // Try up to 5 times
startingDelay: 200, // Start with 200ms delay
timeMultiple: 2, // Double delay each time
maxDelay: 5000, // Max 5 seconds delay
jitter: 'full',
retry: (e, attemptNumber) => {
console.warn(`Retry attempt ${attemptNumber}: ${e.message}`);
return true; // Always retry for now
}
}
);
console.log('Successfully fetched weather:', response);
} catch (e: any) {
console.error('Failed to fetch weather after multiple retries:', e.message);
} finally {
console.log(`Total attempts: ${attemptCount}`);
}
}
main();