async-retry vs p-retry vs promise-poller vs promise-retry vs retry vs retry-axios
Implementing Resilient Retry Logic in JavaScript Applications
async-retryp-retrypromise-pollerpromise-retryretryretry-axiosSimilar Packages:

Implementing Resilient Retry Logic in JavaScript Applications

This comparison evaluates six popular npm packages designed to handle retry logic for asynchronous operations in JavaScript and Node.js environments. These libraries provide mechanisms to automatically re-execute failed promises or functions, often with configurable delays, exponential backoff, and error filtering. While they share a common goal of improving system resilience against transient failures (like network timeouts or rate limits), they differ significantly in their API design, dependency requirements, and specific use cases—ranging from generic promise wrappers to Axios-specific interceptors.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
async-retry01,915-305 years agoMIT
p-retry01,00725.5 kB1a month agoMIT
promise-poller0119-87 years agoMIT
promise-retry0318-116 years agoMIT
retry01,259-205 years agoMIT
retry-axios050464.9 kB113 days agoApache-2.0

Implementing Resilient Retry Logic: A Deep Dive into JavaScript Retry Libraries

In distributed systems and web applications, transient failures are inevitable. Network timeouts, rate limits, and temporary service unavailability require robust retry mechanisms. The JavaScript ecosystem offers several packages to handle this, but they serve different architectural needs. This analysis compares async-retry, p-retry, promise-poller, promise-retry, retry, and retry-axios to help you select the right tool for your resilience strategy.

🔄 Core Execution Model: Wrappers vs. Pollers vs. Interceptors

The fundamental difference lies in how these libraries approach the problem. Most are wrappers around a function call, but some are designed for polling or specific HTTP clients.

async-retry wraps an async function and retries it if it throws. It is designed for Node.js and uses async/await.

import retry from 'async-retry';

await retry(
  async (bail) => {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error('Failed');
    return res;
  },
  { retries: 3 }
);

p-retry is similar but focuses on Promise compatibility and allows aborting via specific errors. It works in browsers and Node.

import pRetry from 'p-retry';

await pRetry(
  async () => {
    const res = await fetch('/api/data');
    if (!res.ok) throw new Error('Failed');
    return res;
  },
  { retries: 3 }
);

promise-poller is different; it polls until a success condition is met, not just on failure. It is for long-running tasks.

import promisePoller from 'promise-poller';

promisePoller({
  task: () => fetch('/api/status').then(r => r.json()),
  validate: (result) => result.status === 'complete',
  interval: 1000,
  retries: 10
});

promise-retry wraps the retry package to return a promise. It supports passing options to the underlying retry logic.

import promiseRetry from 'promise-retry';

await promiseRetry(
  (retry, number) => {
    return fetch('/api/data').catch(retry);
  },
  { retries: 3 }
);

retry is the low-level callback-based foundation. It requires manual promise wrapping for modern usage.

import retry from 'retry';

const operation = retry.operation({ retries: 3 });

operation.attempt(async (currentAttempt) => {
  try {
    await fetch('/api/data');
    operation.mainCallback();
  } catch (err) {
    if (operation.retry(err)) return;
    operation.mainCallback(err);
  }
});

retry-axios is an Axios interceptor. It automatically retries requests configured on the Axios instance.

import retry from 'retry-axios';

axios.defaults.retry = { retries: 3 };
axios.interceptors.request.use(retry.interceptor);

// Any axios request now has retry logic
await axios.get('/api/data');

⏱️ Delay Strategies: Exponential Backoff and Timing

Handling retry timing is critical to avoid overwhelming services. Most libraries support exponential backoff, but configuration varies.

async-retry supports minTimeout, maxTimeout, and factor for exponential backoff.

await retry(async () => { /* ... */ }, {
  minTimeout: 100,
  maxTimeout: 5000,
  factor: 2
});

p-retry supports similar options via the retry package underneath, allowing minTimeout and factor.

await pRetry(async () => { /* ... */ }, {
  minTimeout: 100,
  factor: 2
});

promise-poller uses a fixed interval by default, though custom strategies can be implemented via the progress callback.

promisePoller({
  task: () => { /* ... */ },
  interval: 2000 // Fixed delay between polls
});

promise-retry passes options directly to the retry module, supporting minTimeout, maxTimeout, and factor.

await promiseRetry((retry) => { /* ... */ }, {
  minTimeout: 100,
  factor: 2
});

retry provides the core logic for timeouts, allowing minTimeout, maxTimeout, factor, and randomize.

const op = retry.operation({
  minTimeout: 100,
  factor: 2
});

retry-axios allows configuring retryDelay which can be a number or a function to calculate delay based on attempt count.

axios.defaults.retry = {
  retries: 3,
  retryDelay: (attemptCount) => attemptCount * 1000
};

🛑 Error Handling and Abortion Logic

Knowing when to stop retrying is as important as retrying itself. Some libraries allow you to "bail" out immediately on specific errors.

async-retry provides a bail function. Calling it stops retries immediately for non-retryable errors.

await retry(async (bail) => {
  const err = new Error('Critical');
  err.doNotRetry = true;
  if (err.doNotRetry) bail(err);
  throw err;
});

p-retry allows throwing specific error types (like pRetry.AbortError) to stop retries immediately.

import pRetry from 'p-retry';

await pRetry(async () => {
  throw new pRetry.AbortError('Critical failure');
});

promise-poller stops when the validate function returns true or retries are exhausted. It does not have a specific "bail" for errors.

promisePoller({
  task: () => { /* ... */ },
  validate: (res) => res.success === true
});

promise-retry relies on the error passed to the retry callback. If you don't call retry(err), it stops.

await promiseRetry((retry, number) => {
  return fetch('/api').catch(err => {
    if (err.status === 400) throw err; // Stop retrying
    return retry(err);
  });
});

retry uses operation.retry(err) which returns false if retries are exhausted or the error is deemed non-retryable by custom logic.

operation.attempt((currentAttempt) => {
  if (err.code === 'CRITICAL') {
    operation.mainCallback(err); // Stop
    return;
  }
  if (operation.retry(err)) return;
});

retry-axios allows a retryCondition function to determine if a specific error should trigger a retry.

axios.defaults.retry = {
  retryCondition: (error) => {
    return error.response.status === 503;
  }
};

📦 Maintenance and Architecture Fit

Choosing a library also means choosing its maintenance status and architectural footprint.

async-retry is widely used in the Node.js ecosystem (e.g., by Vercel/Zeit). It is stable and focused on server-side async operations.

// Stable API, widely adopted in Node services
import retry from 'async-retry';

p-retry is actively maintained and works well in modern frontend build pipelines. It is a safe bet for isomorphic apps.

// Good for frontend + backend shared code
import pRetry from 'p-retry';

promise-poller is specialized. It is not a general retry library but a polling library. Use it only for state checking.

// Specialized for polling, not general retries
import promisePoller from 'promise-poller';

promise-retry has seen less activity recently. It is functional but p-retry is often preferred for new Promise-based work.

// Functional but consider p-retry for new projects
import promiseRetry from 'promise-retry';

retry is the underlying engine for many others. It is stable but callback-based, which adds boilerplate in async/await code.

// Core utility, often wrapped by other libs
import retry from 'retry';

retry-axios is specific to Axios. If you switch HTTP clients, this dependency becomes useless. It reduces boilerplate for Axios users.

// Tightly coupled to Axios
import retry from 'retry-axios';

📊 Summary: Key Differences

Featureasync-retryp-retrypromise-pollerpromise-retryretryretry-axios
Primary UseGeneral AsyncGeneral PromisePollingGeneral PromiseCore LogicAxios Interceptor
EnvironmentNode.jsUniversalUniversalUniversalUniversalNode/Browser (Axios)
API StyleAsync/AwaitAsync/AwaitConfig ObjectAsync/AwaitCallbacksInterceptor
Abort Logicbail()AbortErrorvalidate()Throw ErrorCustom LogicretryCondition
BackoffYesYesFixed IntervalYesYesCustom Function

💡 The Big Picture

For general-purpose retry logic in modern JavaScript, p-retry is often the best balance of features and environment compatibility. It handles async/await cleanly and allows easy abortion of retries.

For Node.js backend services, async-retry is a robust standard with a strong track record in production environments.

If you are already using Axios, retry-axios saves significant boilerplate by applying retry logic globally via interceptors rather than wrapping every call.

Avoid retry for new high-level code unless you need the callback control, and use promise-poller only when you are specifically polling for a state change, not retrying a failed action.

Final Thought: Resilience is not just about retrying; it is about retrying smartly. Choose the library that gives you the control you need to distinguish between a transient blip and a permanent failure.

How to Choose: async-retry vs p-retry vs promise-poller vs promise-retry vs retry vs retry-axios

  • async-retry:

    Choose async-retry if you are working in a Node.js environment and need a robust, battle-tested solution that supports async/await natively. It is ideal for backend services or Node-based tooling where you need fine-grained control over retry attempts and error handling without adding heavy dependencies.

  • p-retry:

    Choose p-retry if you want a lightweight, promise-centric library that works well in both browser and Node environments. It is best suited for frontend applications or isomorphic code where you need simple retry logic with exponential backoff and the ability to abort retries based on specific error conditions.

  • promise-poller:

    Choose promise-poller if your use case involves polling a resource until a specific condition is met, rather than simply retrying a failed operation. It is the right choice for scenarios like waiting for a build to complete or checking for a status change, where the operation itself isn't failing but needs repeated execution.

  • promise-retry:

    Choose promise-retry if you are maintaining legacy code or need compatibility with older callback-style patterns, though be aware it is less actively maintained. It is suitable for projects that already depend on the retry package and need a promise wrapper, but new projects should consider more modern alternatives.

  • retry:

    Choose retry if you need the foundational logic for retrying operations and are comfortable wrapping it yourself or using it in a callback-heavy codebase. It is the core dependency for many other retry libraries, but for most modern async/await projects, higher-level wrappers like p-retry or async-retry are recommended.

  • retry-axios:

    Choose retry-axios if your project heavily relies on the Axios HTTP client and you want to add retry logic globally via interceptors. It is the most efficient choice for handling HTTP-specific transient errors (like 503 or 429) without wrapping every individual request call manually.

README for async-retry

async-retry

Retrying made simple, easy, and async.

Usage

// 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,
  }
);

API

retry(retrier : Function, opts : Object) => Promise
  • The supplied function can be async or not. In other words, it can be a function that returns a Promise or a value.
  • The supplied function receives two parameters
    1. A Function you can invoke to abort the retrying (bail)
    2. A Number identifying the attempt. The absolute first attempt (before any retries) is 1.
  • The 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.

Authors