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 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 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 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 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.
Retrying made simple, easy, and async.
// Packages
const retry = require('async-retry');
const fetch = require('node-fetch');
await retry(
async (bail) => {
// if anything throws, we retry
const res = await fetch('https://google.com');
if (403 === res.status) {
// don't retry upon 403
bail(new Error('Unauthorized'));
return;
}
const data = await res.text();
return data.substr(0, 500);
},
{
retries: 5,
}
);
retry(retrier : Function, opts : Object) => Promise
async or not. In other words, it can be a function that returns a Promise or a value.Function you can invoke to abort the retrying (bail)Number identifying the attempt. The absolute first attempt (before any retries) is 1.opts are passed to node-retry. Read its docs
retries: The maximum amount of times to retry the operation. Default is 10.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 true.onRetry: an optional Function that is invoked after a new retry is performed. It's passed the Error that triggered it as a parameter.