fetch-retrier
v0.4.2
Published
A lightweight wrapper around `fetch` that adds **retries**, **per-attempt timeout**, **full jitter** backoff, and **option validation**. Pass standard `RequestInit` options (`method`, `body`, `credentials`, and more) for POST/PUT APIs and other HTTP calls
Readme
Fetch Retrier
A lightweight wrapper around fetch that adds retries, per-attempt timeout, full jitter backoff, and option validation. Pass standard RequestInit options (method, body, credentials, and more) for POST/PUT APIs and other HTTP calls that may be rate-limited or temporarily unavailable.
Features
- Configurable retries – Set the maximum number of attempts per request (
retries >= 1). - Per-attempt timeout – Abort each attempt when it exceeds a given duration (
timeoutMs > 0). - Full jitter backoff – Exponential backoff with random jitter (AWS-style) between retries (
baseBackoffMs >= 0). - Option validation – Invalid numeric options throw
FetchRetrierInvalidOptionsErrorat call time. - RequestInit forwarding – Pass
method,body,credentials,redirect, and otherfetchoptions viainiton every attempt. - Header shorthand – Optional top-level
headersoverrideinit.headerswhen both are set. - Default retry policy – Retries transient HTTP statuses (408, 425, 429, 500, 502, 503, 504) via
defaultShouldRetryandDEFAULT_RETRYABLE_HTTP_STATUSES. - Extensible retry predicate – Compose
defaultShouldRetrywith customshouldRetrylogic. - External cancellation – Pass an
AbortSignalto cancel in-flight requests. - Typed errors –
FetchRetrierHttpError,FetchRetrierNetworkError,FetchRetrierAbortError,FetchRetrierInvalidOptionsError, and related classes. - TypeScript – Exported types including
RequestOptionsandFetchInitOptions.
Installation
npm
npm install fetch-retrieryarn
yarn add fetch-retrierUsage
GET request
import { fetchRetrier, RequestOptions } from 'fetch-retrier';
const options: RequestOptions = {
retries: 3,
timeoutMs: 5000,
baseBackoffMs: 1000,
headers: {
Authorization: 'Bearer token',
},
};
const response = await fetchRetrier('https://api.example.com/data', options);
if (response.ok) {
const data = await response.json();
}POST with JSON body (init)
import { fetchRetrier } from 'fetch-retrier';
const response = await fetchRetrier('https://api.example.com/items', {
retries: 3,
timeoutMs: 5000,
baseBackoffMs: 1000,
init: {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'example' }),
credentials: 'include',
},
});
const item = await response.json();The same init (including body) is applied on every retry attempt. Per-attempt signal and timeout are managed internally.
Custom retry logic
Extend the default predicate instead of reimplementing the status list:
import {
DEFAULT_RETRYABLE_HTTP_STATUSES,
defaultShouldRetry,
fetchRetrier,
} from 'fetch-retrier';
const response = await fetchRetrier('https://api.example.com/data', {
retries: 5,
timeoutMs: 10000,
baseBackoffMs: 500,
shouldRetry: (res, body) => {
if (defaultShouldRetry(res, body)) return true;
if (res.status === 200 && body.includes('"retry": true')) return true;
return false;
},
});
// DEFAULT_RETRYABLE_HTTP_STATUSES is [408, 425, 429, 500, 502, 503, 504]Cancellation with AbortController
const controller = new AbortController();
setTimeout(() => controller.abort(), 250);
await fetchRetrier('https://api.example.com/data', {
retries: 3,
timeoutMs: 5000,
baseBackoffMs: 250,
signal: controller.signal,
});Retry and error behavior
- Success – If
response.okis true, the response is returned immediately. - Invalid options – If
retries < 1,timeoutMs <= 0, orbaseBackoffMs < 0,FetchRetrierInvalidOptionsErroris thrown before any request is made. - Retriable failure – If the response is not OK and
shouldRetry(response, body)returns true, the client waits (full jitter backoff) and retries untilretriesis exhausted. On the last attempt,FetchRetrierHttpErroris thrown (includesstatus). - Non-retriable failure – If
shouldRetryreturns false,FetchRetrierHttpErroris thrown immediately (e.g.Non-retriable HTTP error: 404). - Timeout – If a request exceeds
timeoutMs, it is aborted and retried untilretriesis exhausted; the final failure isFetchRetrierAbortError. - Network / TypeError – Network errors are retried with backoff; after the last attempt,
FetchRetrierNetworkErroris thrown with the original error ascause. - Already aborted signal – If
signalis already aborted before an attempt starts,FetchRetrierAlreadyAbortedErroris thrown (no attempt is made).
Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| retries | number | Yes | Maximum number of attempts (including the first request). Must be >= 1. |
| timeoutMs | number | Yes | Timeout in milliseconds for each attempt. Exceeded attempts are aborted and retried. Must be > 0. |
| baseBackoffMs | number | Yes | Base delay in milliseconds for backoff. Delay is capped at baseBackoffMs * 2^attempt and randomized (full jitter). Must be >= 0 (0 skips backoff delay). |
| init | FetchInitOptions | No | fetch options forwarded to every attempt: method, body, credentials, redirect, mode, cache, etc. signal is reserved for internal timeout and cancellation. |
| headers | Record<string, string> | No | Headers sent on every attempt. Overrides init.headers when both are set. |
| signal | AbortSignal | No | External abort signal. If already aborted, FetchRetrierAlreadyAbortedError is thrown. If aborted during an attempt, the request is aborted and retried until retries is exhausted. |
| shouldRetry | (response: Response, body: string) => boolean | No | Called after response.text() when response.ok is false. Return true to retry. Default: defaultShouldRetry (statuses in DEFAULT_RETRYABLE_HTTP_STATUSES: 408, 425, 429, 500, 502, 503, 504). |
Exported helpers
| Export | Description |
|--------|-------------|
| defaultShouldRetry | Default shouldRetry predicate; compose with custom logic. |
| DEFAULT_RETRYABLE_HTTP_STATUSES | Readonly list of HTTP status codes retried by default. |
Requirements
- Node.js >= 20.0.0
- Uses the global
fetch(available in Node 18+)
License
This project is licensed under the Apache-2.0 License.
