Retry
Rezo includes a built-in retry system for handling transient failures. Configure it as a simple boolean, a retry count, or a detailed object with backoff strategies, status code filters, and lifecycle callbacks.
Quick Start
import { Rezo } from 'rezo';
// Simple: retry up to 3 times
const { data } = await rezo.get('https://api.example.com/data', {
retry: 3
});
// Boolean: use default retry settings (3 retries, 1s delay)
const { data } = await rezo.get(url, { retry: true });
// Detailed configuration
const { data } = await rezo.get(url, {
retry: {
maxRetries: 5,
retryDelay: 1000,
backoff: 'exponential',
maxDelay: 30000,
statusCodes: [408, 429, 500, 502, 503, 504],
retryOnTimeout: true,
retryOnNetworkError: true
}
}); Configuration Formats
The retry option accepts three formats:
Boolean
retry: true enables retries with all defaults: 3 attempts, 1 second delay, no backoff, standard status codes.
retry: false or retry: undefined disables retries entirely.
Number
retry: 5 retries up to 5 times with default settings for everything else.
Object
Full configuration with all available options:
interface RetryConfig {
maxRetries?: number; // Alias: limit
retryDelay?: number; // Alias: delay
maxDelay?: number;
backoff?: number | 'exponential' | 'linear' | ((attempt, baseDelay) => number);
statusCodes?: number[]; // Alias: retryOn
retryOnTimeout?: boolean;
retryOnNetworkError?: boolean;
methods?: HttpMethod[];
condition?: (error, attempt) => boolean | Promise<boolean>;
onRetry?: (error, attempt, delay) => boolean | void | Promise<boolean | void>;
onRetryExhausted?: (error, totalAttempts) => void | Promise<void>;
} Configuration Reference
maxRetries / limit
Maximum number of retry attempts. Both names are accepted; limit is an alias for maxRetries.
retry: { maxRetries: 5 }
// or equivalently:
retry: { limit: 5 } Default: 3
retryDelay / delay
Base delay between retries in milliseconds. This is the starting delay before any backoff multiplier is applied. Both names are accepted.
retry: { retryDelay: 2000 }
// or equivalently:
retry: { delay: 2000 } Default: 1000 (1 second)
maxDelay
Maximum delay between retries in milliseconds. Caps the computed delay even when using exponential backoff, preventing unreasonably long waits.
retry: {
retryDelay: 1000,
backoff: 'exponential',
maxDelay: 30000 // Never wait more than 30 seconds
} Default: 30000 (30 seconds)
backoff
Controls how the delay changes between retry attempts. Accepts several formats:
Exponential Backoff
Multiplies the delay by 2 on each attempt: 1s, 2s, 4s, 8s, 16s…
retry: { retryDelay: 1000, backoff: 'exponential' }
// 'exponential' is equivalent to backoff: 2 Linear Backoff
Adds the base delay on each attempt: 1s, 2s, 3s, 4s, 5s…
retry: { retryDelay: 1000, backoff: 'linear' } Custom Multiplier
Any number acts as the exponential base. For example, backoff: 3 produces: 1s, 3s, 9s, 27s…
retry: { retryDelay: 1000, backoff: 3 }
// Attempt 1: 1000ms
// Attempt 2: 3000ms
// Attempt 3: 9000ms Custom Function
Full control over delay calculation. Receives the attempt number (1-indexed) and the base delay.
retry: {
retryDelay: 1000,
backoff: (attempt, baseDelay) => {
// Fibonacci-like: 1s, 1s, 2s, 3s, 5s...
return Math.min(fib(attempt) * baseDelay, 30000);
}
} No Backoff
backoff: 1 (the default) means constant delay — every retry waits the same amount.
Default: 1 (no backoff, constant delay)
Jitter: All computed delays have +/-10% jitter applied automatically to prevent thundering herd problems when many clients retry simultaneously.
statusCodes / retryOn
HTTP status codes that trigger a retry. Both names are accepted.
retry: { statusCodes: [429, 500, 502, 503, 504] }
// or equivalently:
retry: { retryOn: [429, 500, 502, 503, 504] } Default: [408, 425, 429, 500, 502, 503, 504, 520]
retryOnTimeout
Whether to retry on timeout errors (ETIMEDOUT, ECONNABORTED, UND_ERR_CONNECT_TIMEOUT, UND_ERR_HEADERS_TIMEOUT).
retry: { retryOnTimeout: true } Default: true
retryOnNetworkError
Whether to retry on network errors (ECONNREFUSED, ECONNRESET, ENOTFOUND, EAI_AGAIN, ETIMEDOUT, ECONNABORTED, EPIPE, EHOSTUNREACH, ENETUNREACH).
retry: { retryOnNetworkError: true } Default: true
methods
HTTP methods that are safe to retry. Non-idempotent methods like POST are excluded by default to prevent duplicate submissions.
retry: { methods: ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE'] } To also retry POST requests (be careful with side effects):
retry: { methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'] } Default: ['GET', 'HEAD', 'OPTIONS', 'PUT', 'DELETE']
condition
Custom predicate called after the built-in checks (status codes, error types) pass. Return true to allow the retry, false to stop retrying.
retry: {
maxRetries: 3,
condition: async (error, attempt) => {
// Only retry if the service says it's recoverable
if (error.response?.data?.recoverable === false) {
return false;
}
// Don't retry if we've been rate limited too many times
if (error.response?.status === 429 && attempt > 1) {
return false;
}
return true;
}
} onRetry
Called before each retry attempt. Receives the error, attempt number (1-indexed), and the computed delay in milliseconds. Return false to cancel the retry.
retry: {
maxRetries: 5,
onRetry: (error, attempt, delay) => {
console.log(`Retry ${attempt}/5 in ${delay}ms: ${error.message}`);
// Return false to cancel this retry
if (isUnrecoverable(error)) return false;
}
} onRetryExhausted
Called when all retry attempts are exhausted and the request is about to fail. Use for alerting, logging, or cleanup.
retry: {
maxRetries: 3,
onRetryExhausted: async (error, totalAttempts) => {
await alerting.send({
level: 'error',
message: `Request failed after ${totalAttempts} attempts: ${error.message}`,
url: error.config?.url
});
}
} Instance-Level Defaults
Set retry configuration on the Rezo instance to apply to all requests. Per-request config overrides instance defaults.
const client = new Rezo({
retry: {
maxRetries: 3,
retryDelay: 1000,
backoff: 'exponential',
maxDelay: 15000,
retryOnTimeout: true,
retryOnNetworkError: true
}
});
// Uses instance defaults
await client.get('/api/users');
// Override for this request: 5 retries with linear backoff
await client.get('/api/critical', {
retry: { maxRetries: 5, backoff: 'linear' }
});
// Disable retry for this request
await client.get('/api/idempotent-check', { retry: false }); When both instance and request retry configs are objects, the request-level values take priority for each field. Unspecified fields fall back to the instance defaults.
The beforeRetry Hook
The hooks system provides a beforeRetry hook that fires before each retry attempt, independent of the onRetry callback in the retry config. Use it for cross-cutting concerns like logging or token refresh.
const client = new Rezo({
retry: { maxRetries: 3, backoff: 'exponential' },
hooks: {
beforeRetry: [
async (config, error, retryCount) => {
console.log(`[Hook] Retry ${retryCount}: ${error.code}`);
// Refresh token before retrying a 401
if (error.response?.status === 401) {
const token = await getNewToken();
config.headers.set('Authorization', `Bearer ${token}`);
}
}
]
}
}); The beforeRetry hook runs after the delay and right before the retry request is dispatched. It receives:
config— the request config (mutable, so you can modify headers, etc.)error— the error that caused the retryretryCount— the current retry number (1 for first retry)
Retry Decision Flow
When a request fails, Rezo evaluates whether to retry in this order:
- Max retries check: Has the attempt count exceeded
maxRetries? If yes, fail. - Method check: Is the request method in the
methodslist? If not, fail. - Status code check: Does the response status match any code in
statusCodes? If yes, retry. - Error code check: Is the error code a timeout error (when
retryOnTimeout: true) or a network error (whenretryOnNetworkError: true)? If yes, retry. - Custom condition: If
conditionis provided, call it. Returntrueto retry,falseto fail. - Delay calculation: Compute delay using
retryDelay,backoff,maxDelay, and jitter. - onRetry callback: Call
onRetryif provided. If it returnsfalse, cancel the retry. - Wait and retry: Sleep for the computed delay, then retry the request.
Practical Examples
API with Exponential Backoff
const api = new Rezo({
baseURL: 'https://api.example.com',
retry: {
maxRetries: 5,
retryDelay: 500,
backoff: 'exponential',
maxDelay: 30000,
statusCodes: [429, 500, 502, 503, 504]
}
});
// Delays: ~500ms, ~1000ms, ~2000ms, ~4000ms, ~8000ms (with jitter)
const { data } = await api.get('/users'); Scraper with Aggressive Retry
const scraper = new Rezo({
retry: {
maxRetries: 10,
retryDelay: 2000,
backoff: 'linear',
maxDelay: 60000,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'],
retryOnTimeout: true,
retryOnNetworkError: true,
onRetry: (error, attempt, delay) => {
console.log(`[${attempt}/10] Retrying in ${delay}ms: ${error.message}`);
},
onRetryExhausted: (error, totalAttempts) => {
console.error(`Gave up after ${totalAttempts} attempts: ${error.config?.url}`);
}
}
}); Conditional Retry Based on Response Body
const client = new Rezo({
retry: {
maxRetries: 3,
condition: async (error, attempt) => {
// Some APIs return 200 with error in body
const body = error.response?.data;
if (body?.error?.retryable === true) return true;
if (body?.error?.code === 'RATE_LIMITED') return true;
return false;
}
}
});