fetch-retrier
v0.3.1
Published
A lightweight wrapper around `fetch` that adds **retries**, **per-attempt timeout**, and **full jitter** backoff. Pass standard `RequestInit` options (`method`, `body`, `credentials`, and more) for POST/PUT APIs and other HTTP calls that may be rate-limit
Readme
Fetch Retrier
A lightweight wrapper around fetch that adds retries, per-attempt timeout, and full jitter backoff. Pass standard RequestInit options (method, body, credentials, and more) for POST/PUT APIs and other HTTP calls that may be rate-limited (429) or temporarily unavailable (5xx).
Features
- Configurable retries – Set the maximum number of attempts per request.
- Per-attempt timeout – Abort each attempt when it exceeds a given duration.
- Full jitter backoff – Exponential backoff with random jitter (AWS-style) between retries.
- RequestInit forwarding – Pass
method,body,credentials,redirect, and otherfetchoptions viainiton every attempt. - Header shorthand – Optional top-level
headersoverrideinit.headerswhen both are set. - Custom retry predicate – Control which responses trigger a retry (default: 429, 500, 502, 503, 504).
- External cancellation – Pass an
AbortSignalto cancel in-flight requests. - Typed errors –
FetchRetrierHttpError,FetchRetrierNetworkError,FetchRetrierAbortError, 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
const response = await fetchRetrier('https://api.example.com/data', {
retries: 5,
timeoutMs: 10000,
baseBackoffMs: 500,
shouldRetry: (res, body) => {
if ([429, 500, 502, 503].includes(res.status)) return true;
if (res.status === 200 && body.includes('"retry": true')) return true;
return false;
},
});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. - 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). |
| timeoutMs | number | Yes | Timeout in milliseconds for each attempt. Exceeded attempts are aborted and retried. |
| baseBackoffMs | number | Yes | Base delay in milliseconds for backoff. Delay is capped at baseBackoffMs * 2^attempt and randomized (full jitter). |
| 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: 429, 500, 502, 503, 504. |
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.
