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


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/[email protected]/build/src/index.js"></script>
<script>
rax.attach();
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/[email protected]/build/src/index.js"></script>
<script>
rax.attach();
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:
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,
retryDelay: 100,
httpMethodsToRetry: ['GET', 'HEAD', 'OPTIONS', 'DELETE', 'PUT'],
statusCodesToRetry: [[100, 199], [429, 429], [500, 599]],
backoffType: 'exponential',
jitter: 'full',
onError: async (err) => {
const cfg = rax.getConfig(err);
console.log(`Error occurred, retry attempt #${cfg.currentRetryAttempt + 1} will happen after backoff`);
},
onRetryAttempt: async (err) => {
const cfg = rax.getConfig(err);
console.log(`Retry attempt #${cfg.currentRetryAttempt} is about to start`);
console.log(`Retries remaining: ${cfg.retriesRemaining}`);
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',
retryDelay: 1000,
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,
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,
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',
retryDelay: 1000
}
Full Jitter
Randomizes the delay between 0 and the calculated exponential backoff:
raxConfig: {
backoffType: 'exponential',
jitter: 'full',
retryDelay: 1000
}
Equal Jitter
Uses half fixed delay, half random:
raxConfig: {
backoffType: 'exponential',
jitter: 'equal',
retryDelay: 1000
}
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) => {
console.log('An error occurred, will retry after backoff');
},
onRetryAttempt: async (err) => {
const token = await refreshToken(err);
window.localStorage.setItem('token', token);
}
}
});
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`);
if (cfg.retriesRemaining === 0) {
showErrorNotification('Request failed after multiple attempts');
}
}
}
});
This is especially useful when chaining retry-axios with other error interceptors:
axios.interceptors.response.use(null, async (error) => {
const cfg = rax.getConfig(error);
if (cfg?.retriesRemaining === 0) {
showUserNotification('An error occurred: ' + error.message);
}
return Promise.reject(error);
});
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,
shouldRetry: err => {
const cfg = rax.getConfig(err);
if (err.response?.status && err.response.status >= 400 && err.response.status < 500) {
return err.response.status === 429;
}
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: {
shouldRetry: err => {
const cfg = rax.getConfig(err);
if (cfg.currentRetryAttempt >= cfg.retry) return false
if (err.response.statusText.includes('Try again')) return true
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);
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}`);
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:
- Return specific HTTP status codes: 1xx (informational), 429 (too many requests), and 5xx (server errors)
- Are network errors without a response: ETIMEDOUT, ENOTFOUND, ECONNABORTED, ECONNRESET, etc.
- 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
评论