Détail du package

retry-axios

JustinBeckwith4.9mApache-2.04.0.1

Retry HTTP requests with Axios.

axios, retry

readme

retry-axios

Use Axios interceptors to automatically retry failed requests. Super flexible. Built in exponential backoff.

retry-axios

NPM Version GitHub Actions Known Vulnerabilities codecov Biome

Installation

npm install retry-axios

CDN

For front-end applications, you can also use retry-axios directly from a CDN without a build step. This is useful for quick prototypes or environments where you can't use npm.

jsDelivr

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/retry-axios@4.0.0/build/src/index.js"></script>
<script>
  // Attach retry-axios to the global axios object
  rax.attach();

  // Now you can use axios with retry capabilities
  axios.get('https://httpbin.org/status/503')
    .then(response => {
      console.log('Success:', response.data);
    })
    .catch(error => {
      console.error('Error:', error.message);
    });
</script>

unpkg

<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="https://unpkg.com/retry-axios@4.0.0/build/src/index.js"></script>
<script>
  // Attach retry-axios to the global axios object
  rax.attach();

  // Now you can use axios with retry capabilities
  axios.get('https://httpbin.org/status/503')
    .then(response => {
      console.log('Success:', response.data);
    })
    .catch(error => {
      console.error('Error:', error.message);
    });
</script>

Usage

To use this library, import it alongside of axios:

// Just import rax and your favorite version of axios
const rax = require('retry-axios');
const axios = require('axios');

Or, if you're using TypeScript / es modules:

import * as rax from 'retry-axios';
import axios from 'axios';

You can attach to the global axios object, and retry 3 times by default:

const interceptorId = rax.attach();
const res = await axios('https://test.local');

Or you can create your own axios instance to make scoped requests:

const myAxiosInstance = axios.create();
myAxiosInstance.defaults.raxConfig = {
  retry: 3
};
const interceptorId = rax.attach(myAxiosInstance);
const res = await myAxiosInstance.get('https://test.local');

You have a lot of options...

const interceptorId = rax.attach();
const res = await axios({
  url: 'https://test.local',
  raxConfig: {
    // Retry 3 times before giving up. Applies to all errors (5xx, network errors, timeouts, etc). Defaults to 3.
    retry: 3,

    // Milliseconds to delay between retries. Defaults to 100.
    // - For 'static': Fixed delay between retries
    // - For 'exponential': Base multiplier for exponential calculation
    // - For 'linear': Ignored (uses attempt * 1000)
    retryDelay: 100,

    // HTTP methods to automatically retry.  Defaults to:
    // ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT']
    httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT'],

    // The response status codes to retry.  Supports a double
    // array with a list of ranges.  Defaults to:
    // [[100, 199], [429, 429], [500, 599]]
    statusCodesToRetry: [[100, 199], [429, 429], [500, 599]],

    // You can set the backoff type.
    // options are 'exponential' (default), 'static' or 'linear'
    backoffType: 'exponential',

    // Jitter strategy for exponential backoff. Defaults to 'none'.
    // Options: 'none', 'full', 'equal'
    // Helps prevent thundering herd in distributed systems
    jitter: 'full',

    // You can detect when an error occurs, before the backoff delay
    onError: async (err) => {
      const cfg = rax.getConfig(err);
      console.log(`Error occurred, retry attempt #${cfg.currentRetryAttempt + 1} will happen after backoff`);
    },

    // You can detect when a retry attempt is about to be made, after the backoff delay
    onRetryAttempt: async (err) => {
      const cfg = rax.getConfig(err);
      console.log(`Retry attempt #${cfg.currentRetryAttempt} is about to start`);
      console.log(`Retries remaining: ${cfg.retriesRemaining}`);

      // Check if this is the final retry attempt
      if (cfg.retriesRemaining === 0) {
        console.log('This is the final retry attempt');
      }
    }
  }
});

Backoff Types and Timing

The backoffType option controls how delays between retry attempts are calculated. There are three strategies available:

Exponential Backoff (default)

Uses the formula: ((2^attempt - 1) / 2) * retryDelay milliseconds

The retryDelay parameter (defaults to 100ms) is used as the base multiplier for the exponential calculation.

Example timing with default retryDelay: 100:

  • Retry 1: 50ms delay
  • Retry 2: 150ms delay
  • Retry 3: 350ms delay
  • Retry 4: 750ms delay
  • Retry 5: 1,550ms delay

Example timing with retryDelay: 1000:

  • Retry 1: 500ms delay
  • Retry 2: 1,500ms delay
  • Retry 3: 3,500ms delay
  • Retry 4: 7,500ms delay
  • Retry 5: 15,500ms delay
raxConfig: {
  backoffType: 'exponential',  // This is the default
  retryDelay: 1000,  // Use 1000ms as the base multiplier
  retry: 5
}

Static Backoff

Uses a fixed delay specified by retryDelay (defaults to 100ms if not set).

Example timing with retryDelay: 3000:

  • Retry 1: 3,000ms delay
  • Retry 2: 3,000ms delay
  • Retry 3: 3,000ms delay
raxConfig: {
  backoffType: 'static',
  retryDelay: 3000,  // 3 seconds between each retry
  retry: 3
}

Linear Backoff

Delay increases linearly: attempt * 1000 milliseconds

The retryDelay option is ignored when using linear backoff.

Example timing for the first 5 retries:

  • Retry 1: 1,000ms delay
  • Retry 2: 2,000ms delay
  • Retry 3: 3,000ms delay
  • Retry 4: 4,000ms delay
  • Retry 5: 5,000ms delay
raxConfig: {
  backoffType: 'linear',
  retry: 5
}

Maximum Retry Delay

You can cap the maximum delay for any backoff type using maxRetryDelay:

raxConfig: {
  backoffType: 'exponential',
  maxRetryDelay: 5000,  // Never wait more than 5 seconds
  retry: 10
}

Jitter

Jitter adds randomness to exponential backoff delays to prevent the "thundering herd" problem where many clients retry at the same time. This is especially useful in distributed systems.

Available jitter strategies (only applies to exponential backoff):

No Jitter (default)

raxConfig: {
  backoffType: 'exponential',
  jitter: 'none',  // or omit this option
  retryDelay: 1000
}
// Retry 1: exactly 500ms
// Retry 2: exactly 1,500ms
// Retry 3: exactly 3,500ms

Full Jitter

Randomizes the delay between 0 and the calculated exponential backoff:

raxConfig: {
  backoffType: 'exponential',
  jitter: 'full',
  retryDelay: 1000
}
// Retry 1: random between 0-500ms
// Retry 2: random between 0-1,500ms
// Retry 3: random between 0-3,500ms

Equal Jitter

Uses half fixed delay, half random:

raxConfig: {
  backoffType: 'exponential',
  jitter: 'equal',
  retryDelay: 1000
}
// Retry 1: 250ms + random(0-250ms) = 250-500ms
// Retry 2: 750ms + random(0-750ms) = 750-1,500ms
// Retry 3: 1,750ms + random(0-1,750ms) = 1,750-3,500ms

Recommendation: Use 'full' jitter for most distributed systems to minimize collision probability while maintaining good retry timing.

Callback Timing

There are two callbacks you can use to hook into the retry lifecycle:

  • onError: Called immediately when an error occurs, before the backoff delay. Use this for logging errors or performing actions that need to happen right away.
  • onRetryAttempt: Called after the backoff delay, just before the retry request is made. Use this for actions that need to happen right before retrying (like refreshing tokens).

Both functions are asynchronous and must return a promise. The retry will wait for the promise to resolve before proceeding. If the promise is rejected, the retry will be aborted:

const res = await axios({
  url: 'https://test.local',
  raxConfig: {
    onError: async (err) => {
      // Called immediately when error occurs
      console.log('An error occurred, will retry after backoff');
    },
    onRetryAttempt: async (err) => {
      // Called after backoff delay, before retry
      const token = await refreshToken(err);
      window.localStorage.setItem('token', token);
      // If refreshToken throws or this promise rejects,
      // the retry will be aborted
    }
  }
});

Tracking Retry Progress

You can track the current retry state using properties available in the configuration:

  • currentRetryAttempt: The number of retries that have been attempted (starts at 0, increments with each retry)
  • retriesRemaining: The number of retries left before giving up (calculated as retry - currentRetryAttempt)

These properties are particularly useful when you want to show different messages or take different actions based on whether this is the final retry attempt:

const res = await axios({
  url: 'https://test.local',
  raxConfig: {
    retry: 3,
    onRetryAttempt: async (err) => {
      const cfg = rax.getConfig(err);

      console.log(`Retry attempt ${cfg.currentRetryAttempt} of ${cfg.retry}`);
      console.log(`${cfg.retriesRemaining} retries remaining`);

      // Show user-facing error only on final retry
      if (cfg.retriesRemaining === 0) {
        showErrorNotification('Request failed after multiple attempts');
      }
    }
  }
});

This is especially useful when chaining retry-axios with other error interceptors:

// Global error handler that shows notifications
axios.interceptors.response.use(null, async (error) => {
  const cfg = rax.getConfig(error);

  // Only show error notification on the final retry attempt
  // Don't spam the user with notifications for intermediate failures
  if (cfg?.retriesRemaining === 0) {
    showUserNotification('An error occurred: ' + error.message);
  }

  return Promise.reject(error);
});

// Attach retry interceptor
rax.attach();

Customizing Retry Logic

You can customize which errors should trigger a retry using the shouldRetry function:

const res = await axios({
  url: 'https://test.local',
  raxConfig: {
    retry: 3,
    // Custom logic to decide if a request should be retried
    // This is called AFTER checking the retry count limit
    shouldRetry: err => {
      const cfg = rax.getConfig(err);

      // Don't retry on 4xx errors except 429
      if (err.response?.status && err.response.status >= 400 && err.response.status < 500) {
        return err.response.status === 429;
      }

      // Retry on network errors and 5xx errors
      return true;
    }
  }
});

If you want to add custom retry logic without duplicating too much of the built-in logic, rax.shouldRetryRequest will tell you if a request would normally be retried:

const res = await axios({
  url: 'https://test.local',
  raxConfig: {
    // Override the decision making process on if you should retry
    shouldRetry: err => {
      const cfg = rax.getConfig(err);
      if (cfg.currentRetryAttempt >= cfg.retry) return false // ensure max retries is always respected

      // Always retry this status text, regardless of code or request type
      if (err.response.statusText.includes('Try again')) return true

      // Handle the request based on your other config options, e.g. `statusCodesToRetry`
      return rax.shouldRetryRequest(err)
    }
  }
});

Accessing All Retry Errors

When retries are exhausted and the request finally fails, you can access the complete history of all errors that occurred during the retry attempts. This is particularly useful for debugging and understanding what went wrong, especially for non-idempotent operations like POST requests where the error may change between attempts.

try {
  await axios.post('https://test.local/api/endpoint', data, {
    raxConfig: {
      httpMethodsToRetry: ['POST'],
      retry: 3
    }
  });
} catch (err) {
  const cfg = rax.getConfig(err);

  // Access all errors encountered during retries
  if (cfg?.errors) {
    console.log(`Total attempts: ${cfg.errors.length}`);
    console.log(`First error: ${cfg.errors[0].response?.status}`);
    console.log(`Last error: ${err.response?.status}`);

    // Log all error details
    cfg.errors.forEach((error, index) => {
      console.log(`Attempt ${index + 1}: ${error.response?.status} - ${error.response?.data}`);
    });
  }
}

The errors array is automatically populated and contains:

  • First element: The initial error that triggered the retry logic
  • Subsequent elements: Errors from each retry attempt
  • Order: Errors are in chronological order (oldest to newest)

This feature is especially valuable when:

  • Debugging complex failure scenarios where errors change between attempts
  • Implementing custom error handling logic that needs to consider all failures
  • Logging and monitoring to understand the full context of request failures
  • Working with non-idempotent operations where side effects may occur

What Gets Retried

By default, retry-axios will retry requests that:

  1. Return specific HTTP status codes: 1xx (informational), 429 (too many requests), and 5xx (server errors)
  2. Are network errors without a response: ETIMEDOUT, ENOTFOUND, ECONNABORTED, ECONNRESET, etc.
  3. Use idempotent HTTP methods: GET, HEAD, PUT, OPTIONS, DELETE

The retry config option controls the maximum number of retry attempts for all error types. If you need different behavior for network errors vs response errors, use the shouldRetry function to implement custom logic.

How it works

This library attaches an interceptor to an axios instance you pass to the API. This way you get to choose which version of axios you want to run, and you can compose many interceptors on the same request pipeline.

License

Apache-2.0

changelog

Changelog

4.0.1 (2025-11-09)

Bug Fixes

  • Switch Codecov to OIDC tokenless authentication (#318) (75e5b6d)

4.0.0 (2025-10-20)

⚠ BREAKING CHANGES

This major release includes several breaking changes that simplify the API and improve consistency. Please review the migration guide below for each change.

1. Node.js Version Requirements

This library now requires Node.js 20 or higher. Previous versions supported Node.js 6, 8, 12, and 14, which are all now end-of-life.

Migration Required: Upgrade your Node.js version to 20 or higher before upgrading to retry-axios 4.0.

# Check your Node.js version
node --version

# If below v20, upgrade Node.js first
# Visit https://nodejs.org or use a version manager like nvm

2. Removal of config.instance Option

The config.instance option has been removed. The axios instance is now automatically used from the interceptor attachment point.

This was confusing because users had to specify the instance twice - once in raxConfig and once in rax.attach(). Now you only specify it once in rax.attach().

Before (v3.x):

const myAxiosInstance = axios.create();
myAxiosInstance.defaults.raxConfig = {
  instance: myAxiosInstance,  // ❌ Remove this
  retry: 3
};
rax.attach(myAxiosInstance);

After (v4.0):

const myAxiosInstance = axios.create();
myAxiosInstance.defaults.raxConfig = {
  retry: 3  // ✅ Instance is automatically used from rax.attach()
};
rax.attach(myAxiosInstance);

Migration Required: Remove the instance property from your raxConfig objects.

3. Simplified Retry Configuration - Removal of noResponseRetries

The noResponseRetries configuration option has been removed. The retry option now controls the maximum number of retries for ALL error types (both response errors like 5xx and network errors like timeouts).

This simplifies the API to match industry standards. Popular libraries like axios-retry, Got, and Ky all use a single retry count.

Before (v3.x):

raxConfig: {
  retry: 3,              // For 5xx response errors
  noResponseRetries: 2   // For network/timeout errors
}

After (v4.0):

raxConfig: {
  retry: 3  // For ALL errors (network + response errors)
}

If you need different behavior for network errors vs response errors, use the shouldRetry callback:

raxConfig: {
  retry: 5,
  shouldRetry: (err) => {
    const cfg = rax.getConfig(err);

    // Network error (no response) - allow up to 5 retries
    if (!err.response) {
      return cfg.currentRetryAttempt < 5;
    }

    // Response error (5xx, 429, etc) - limit to 2 retries
    return cfg.currentRetryAttempt < 2;
  }
}

Migration Required:

  • If you used noResponseRetries, remove it and adjust your retry value as needed
  • If you need different retry counts for different error types, implement a shouldRetry function

4. onRetryAttempt Now Requires Async Functions

The onRetryAttempt callback must now return a Promise. It will be awaited before the retry attempt proceeds. If the Promise is rejected, the retry will be aborted.

Additionally, the timing has changed: onRetryAttempt is now called AFTER the backoff delay (right before the retry), not before. A new onError callback has been added that fires immediately when an error occurs.

Before (v3.x):

raxConfig: {
  onRetryAttempt: (err) => {
    // Synchronous callback, called before backoff delay
    console.log('About to retry');
  }
}

After (v4.0):

raxConfig: {
  // Called immediately when error occurs, before backoff delay
  onError: async (err) => {
    console.log('Error occurred, will retry after backoff');
  },

  // Called after backoff delay, before retry attempt
  onRetryAttempt: async (err) => {
    console.log('About to retry now');
    // Can perform async operations like refreshing tokens
    const token = await refreshAuthToken();
    // If this throws, the retry is aborted
  }
}

Common use case - Refreshing authentication tokens:

raxConfig: {
  retry: 3,
  onRetryAttempt: async (err) => {
    // Refresh expired token before retrying
    if (err.response?.status === 401) {
      const newToken = await refreshToken();
      // Update the authorization header for the retry
      err.config.headers.Authorization = `Bearer ${newToken}`;
    }
  }
}

Migration Required:

  • Change onRetryAttempt to be an async function or return a Promise
  • If you need immediate error notification (old onRetryAttempt timing), use the new onError callback instead
  • If your callback throws or rejects, be aware this will now abort the retry

Summary of All Breaking Changes

  1. Node.js 20+ required - Drops support for Node.js 6, 8, 12, and 14
  2. Remove config.instance - Axios instance is now automatically used from rax.attach()
  3. Remove noResponseRetries - Use retry for all error types, or implement shouldRetry for custom logic
  4. onRetryAttempt must be async - Must return a Promise, called after backoff delay (use onError for immediate notification)

Features

  • accept promises on config.onRetryAttempt (#23) (acfbe39)
  • add configurable backoffType (#76) (6794d85)
  • Add jitter support and use retryDelay as base for exponential backoff (#314) (7436b59)
  • Add retriesRemaining property to track remaining retry attempts (#316) (2d1f46b)
  • add support for cjs (#291) (38244be)
  • add support for configurable http methods (819855c)
  • add support for noResponseRetries (d2cfde7)
  • add support for onRetryAttempt handler (fa17de4)
  • add support for overriding shouldRetry (76fcff5)
  • add support for statusCodesToRetry (9283c9e)
  • allow retryDelay to be 0 (#132) (57ba46f)
  • Collect all errors in errors array during retry attempts (#315) (a7ae9e1)
  • configurable maxRetryDelay (#165) (b8842d7)
  • drop support for node.js 6, add 12 (78ea044)
  • export the shouldRetryRequest method (#74) (694d638)
  • produce es, common, and umd bundles (#107) (62cabf5)
  • Remove redundant config.instance option (#312) (402723d)
  • ship source maps (#223) (247fae0)
  • Simplify retry configuration API (#311) (cb447b3)
  • support retry-after header (#142) (5c6cace)
  • support the latest versions of node.js (#188) (ef74217)
  • umd compatibility with babel 7.x (#21) (f1b336c)

Bug Fixes

  • added check for cancel tokens (#99) (734a93f)
  • Call onRetryAttempt after backoff timeout (#307) (a5457e4)
  • cannot set propery raxConfig of undefined (#114) (0be8578)
  • deps: update dependency gts to v1 (#45) (1dc0f2f)
  • Don't store counter on input config object (#98) (c8ceec0), closes #61
  • ensure config is set (#81) (88ffd00)
  • fix instructions and test for non-static instnaces (544c2a6)
  • Fix potential exception when there is no response (#258) (a58cd1d)
  • Fix workaround for arrays that are passed as objects (#238) (6e2454a)
  • handle arrays that are converted to objects (#83) (554fd4c)
  • include files in the release (#29) (30663b3)
  • non-zero delay between first attempt and first retry for linear and exp strategy (#163) (e63ca08)
  • onRetryAttempt does not handle promise rejection (#306) (6f5ecc2)
  • preserve configuration for custom instance (#240) (2e4e702)
  • Respect retry limit when using custom shouldRetry (#309) (58f6fa6)
  • typescript: include raxConfig in native axios types (#85) (b8b0456)
  • Update tsconfig.json to emit index.d.ts (#229) (bff6aa9)

Miscellaneous Chores

Build System