These libraries provide automated retry logic for operations that might fail temporarily, such as network requests or database connections. Instead of writing manual loops or complex error handling code, developers can use these tools to define how many times an action should be repeated and how long to wait between attempts. While they share a common goal, they differ in their API style — some use callbacks, others use Promises, and one is built specifically for the Axios HTTP client.
When building reliable software, things will fail — networks drop, APIs rate-limit, and databases lock. Instead of crashing, your application should try again. These five packages solve that problem, but they approach it in different ways. Let's look at how they handle API styles, configuration, error control, and specific use cases.
The biggest difference is how you write your code. Modern JavaScript uses async/await, but older tools rely on callbacks.
retry uses a callback pattern. You create an operation and pass a function that accepts a callback.
const retry = require('retry');
const operation = retry.operation();
operation.attempt(function(currentAttempt) {
fetchData(function(err, result) {
if (operation.retry(err)) {
return;
}
console.log(operation.mainError());
});
});
async-retry wraps this logic in a Promise. You pass an async function directly.
const retry = require('async-retry');
await retry(async (bail) => {
const result = await fetchData();
return result;
}, { retries: 3 });
p-retry also uses Promises but focuses on standard async functions.
const pRetry = require('p-retry');
await pRetry(async () => {
const result = await fetchData();
return result;
}, { retries: 3 });
promise-retry passes a retry function into your Promise. You decide when to call it.
const promiseRetry = require('promise-retry');
await promiseRetry((retry, number) => {
return fetchData().catch(retry);
}, { retries: 3 });
retry-axios is different — it attaches to an Axios instance. You do not wrap calls manually.
const axios = require('axios');
const retry = require('retry-axios');
const client = axios.create();
retry.attach({ client });
// Normal call, but retries automatically on failure
await client.get('/api/data');
All libraries let you set how many times to try and how long to wait. Most use exponential backoff — waiting longer after each failure.
retry configures timeouts on the operation object.
const operation = retry.operation({
retries: 3,
factor: 2,
minTimeout: 1000,
maxTimeout: 5000
});
async-retry passes these options as the second argument.
await retry(async () => { /*...*/ }, {
retries: 3,
factor: 2,
minTimeout: 1000,
maxTimeout: 5000
});
p-retry uses a very similar options object.
await pRetry(async () => { /*...*/ }, {
retries: 3,
minTimeout: 1000,
maxTimeout: 5000
});
promise-retry supports forever mode for infinite retries.
await promiseRetry((retry) => { /*...*/ }, {
retries: 3,
factor: 2,
minTimeout: 1000,
forever: false
});
retry-axios sets defaults on the Axios config object.
const client = axios.create({
retryConfig: {
retries: 3,
retryDelay: 1000
}
});
Sometimes an error is permanent — like a 404 Not Found. Retrying won't help. You need a way to stop early.
retry requires you to check the error manually and call operation.retry(err).
operation.attempt(function(currentAttempt) {
fetchData(function(err) {
if (err && err.code === 'NOT_FOUND') {
// Must manually finalize
return operation.finalize(err);
}
if (operation.retry(err)) return;
});
});
async-retry lets you call bail() to stop immediately.
await retry(async (bail) => {
const err = await checkStatus();
if (err && err.permanent) {
bail(err); // Stops retries
return;
}
throw new Error('Try again');
});
p-retry allows throwing specific error types to abort.
await pRetry(async () => {
const err = await checkStatus();
if (err && err.permanent) {
throw new pRetry.AbortError(err); // Stops retries
}
throw new Error('Try again');
});
promise-retry relies on not calling the retry function.
await promiseRetry((retry) => {
return fetchData().catch(err => {
if (err.permanent) throw err; // Don't call retry()
return retry(err);
});
});
retry-axios uses a shouldRetry callback in config.
const client = axios.create({
retryConfig: {
shouldRetry: (err) => {
if (err.response.status === 404) return false;
return true;
}
}
});
Some tools work for any code — database queries, file reads, or APIs. Others are built just for HTTP.
retry, async-retry, p-retry, and promise-retry are generic. They wrap any function.
// Works for DB, File, or Network
await pRetry(async () => {
await db.query('SELECT * FROM users');
});
retry-axios only works with Axios. You cannot use it for database calls.
// Only works for Axios requests
await client.get('/users');
// Cannot wrap db.query() here
| Feature | retry | async-retry | p-retry | promise-retry | retry-axios |
|---|---|---|---|---|---|
| Style | Callback | Promise | Promise | Promise + Trigger | Interceptor |
| Async/Await | ❌ No | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Abort Logic | Manual Check | bail() | AbortError | Skip retry() | shouldRetry |
| Scope | Generic | Generic | Generic | Generic | Axios Only |
| Best For | Legacy Code | Serverless | General App | Custom Control | Axios Projects |
For most modern web applications, p-retry or async-retry are the best choices. They fit naturally into async/await code and are easy to read. If you are already using Axios for all your network calls, retry-axios saves you from wrapping every single request.
Avoid retry for new work — the callback style makes code harder to follow. Use promise-retry only if you need its unique ability to trigger retries from inside the function logic.
Pick the tool that matches your existing stack. Consistency matters more than minor feature differences.
Choose retry only if you are maintaining legacy code that relies on callbacks instead of Promises. For new projects, this library is generally not recommended because it forces you to write older-style code. It serves as the foundation for many other retry libraries but lacks modern async/await support.
Choose p-retry if you are already using other utilities from the Sindre Sorhus ecosystem or need a robust, well-tested Promise library. It offers detailed error reporting through its onFailedAttempt callback. This package is ideal for general-purpose Node.js or frontend applications using async/await.
Choose promise-retry if you need fine-grained control over when a retry happens inside the operation itself. Unlike others that simply re-run the function, this passes a retry trigger into your code. It is often used in package managers and tools requiring specific network resilience patterns.
Choose async-retry if you want a lightweight, Promise-based wrapper that is easy to read and works well in serverless environments. It is a solid choice for Next.js or Vercel projects where simplicity is key. It handles async functions cleanly without requiring extra boilerplate code.
Choose retry-axios if your project already depends heavily on Axios for HTTP requests and you want to add retry logic globally. It works as an interceptor, so you do not need to wrap individual calls. This is the most efficient option for Axios-specific workflows but cannot be used with other HTTP clients.
Abstraction for exponential and custom retry strategies for failed operations.
npm install retry
This module has been tested and is ready to be used.
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,
});
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.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:

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 factor5 * 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
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.
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.
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.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.
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.
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.
This is an alias for retryOperation.attempt(fn). This is deprecated. Please use retryOperation.attempt(fn) instead.
This is an alias for retryOperation.attempt(fn). This is deprecated. Please use retryOperation.attempt(fn) instead.
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.
Allows you to stop the operation being retried. Useful for aborting the operation on a fatal error etc.
Resets the internal state of the operation object, so that you can call attempt() again as if this was a new operation object.
Returns an int representing the number of attempts it took to call fn before it was successful.
retry is licensed under the MIT license.
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().