createResilientFetch() returns a function with the same signature as fetch. Every request goes through a total timeout, retries with backoff and jitter, and a circuit breaker per host. Use it for any outbound HTTP call where you would otherwise write the same retry loop again. It needs no dependencies: it wraps globalThis.fetch (Node ≥ 20, Bun, Deno, Workers, browsers).
A drop-in fetch with timeouts, retries and a circuit breaker per host:
const http = createResilientFetch({ name: 'payments' }); const res = await http(url, { resilience: { retries: 1 } });
Responses are returned as-is (a final 503 resolves, like fetch); only network errors and resilience rejections throw.
A drop-in fetch with timeouts, retries and a circuit breaker per host:
const http = createResilientFetch({ name: 'payments' }); const res = await http(url, { resilience: { retries: 1 } });
Responses are returned as-is (a final 503 resolves, like fetch); only network errors and resilience rejections throw.
The ok read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.
Total timeout bounds the whole call, retries and their delays included.
Retry repeats network errors, timeouts and 408 429 500 502 503 504 responses. A Retry-After header (seconds or an HTTP date) replaces the computed delay.
Circuit breaker per host (url.origin): opens when 50% of the last 20 calls failed (once at least 10 were seen) and stays open 10 s. A dead host doesn’t block calls to healthy ones.
Attempt timeout bounds each single attempt, so one hung connection doesn’t use up the total budget.
Responses come back as they are. A final 503 resolves like it does with fetch; only network errors and resilience rejections (TimeoutRejectedError, BrokenCircuitError, BulkheadRejectedError) throw. A response that is discarded for a retry has its body cancelled first, so the connection goes back to the pool instead of waiting for garbage collection.
The ok read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not.
A drop-in fetch with timeouts, retries and a circuit breaker per host:
const http = createResilientFetch({ name: 'payments' }); const res = await http(url, { resilience: { retries: 1 } });
Responses are returned as-is (a final 503 resolves, like fetch); only network errors and resilience rejections throw.
A drop-in fetch with timeouts, retries and a circuit breaker per host:
const http = createResilientFetch({ name: 'payments' }); const res = await http(url, { resilience: { retries: 1 } });
Responses are returned as-is (a final 503 resolves, like fetch); only network errors and resilience rejections throw.
The fetch to wrap (undici’s, a mock, an instrumented one).
pipeline
ResiliencePipeline<Response>
built in
Your own pipeline instead. Per-request retries and the per-host split are then up to you.
onBeforeAttempt
(request, context) => Request | void
none
Called before every attempt with a fresh Request; return a replacement.
retryOn as a function receives { response, error, attemptNumber }: response is set when one arrived, error when the attempt threw. The circuit breaker ignores retryOn and always counts network errors, timeouts and 408 429 500 502 503 504 as failures.
A request that may have reached the server is only repeated when repeating it is safe:
GET, HEAD, OPTIONS, TRACE, PUT and DELETE are retried.
POST, PATCH and any other method are retried only with an Idempotency-Key header, resilience: { idempotent: true }, or retryUnsafeMethods: true.
A request whose body is a stream (ReadableStream, a Node stream or async iterable; for axios, a stream data) is never repeated once sent, whatever the rules above: the body can only be read once. The failed response or error is returned as is. Buffer the body (string, Uint8Array, Blob) to make it retryable.
A POST that timed out may have charged a card; retrying it could charge it twice. Send an Idempotency-Key when the API supports one (Stripe, Adyen and most payment APIs do):
The randomUUID() method of the Crypto interface is used to generate a v4 UUID using a cryptographically secure random number generator.
Available only in secure contexts.
onBeforeAttempt runs before every attempt, retries included. It gets a new Request (a Request input is cloned each time, so its body can be sent again) and the execution context. Return a replacement, or nothing to send it unchanged.
A drop-in fetch with timeouts, retries and a circuit breaker per host:
const http = createResilientFetch({ name: 'payments' }); const res = await http(url, { resilience: { retries: 1 } });
Responses are returned as-is (a final 503 resolves, like fetch); only network errors and resilience rejections throw.
Called before every attempt with a fresh Request; return a replacement, e.g. with a refreshed auth token.
onBeforeAttempt: async (
request: Request
request, {
attemptNumber: number
0 for the first attempt, then 1, 2, … in the order attempts start. Use it for logging or per-attempt idempotency keys.
One counter per execution numbers every retry and hedged attempt, however retry and hedging are nested
(retry around hedging, retry inside hedged branches), so a number is never repeated within an execution.
Strategies' own counters (RetryPredicateArgs.attemptNumber, OnHedgingArgs.attemptNumber) stay local.
attemptNumber }) => {
const
constheaders:Headers
headers=new
var Headers:new (init?:HeadersInit) =>Headers
The Headers interface of the Fetch API allows you to perform various actions on HTTP request and response headers.
The set() method of the Headers interface sets a new value for an existing header inside a Headers object, or adds the header if it does not already exist.
The set() method of the Headers interface sets a new value for an existing header inside a Headers object, or adds the header if it does not already exist.
Allows manipulation and formatting of text strings and determination and location of substrings within strings.
String(
attemptNumber: number
0 for the first attempt, then 1, 2, … in the order attempts start. Use it for logging or per-attempt idempotency keys.
One counter per execution numbers every retry and hedged attempt, however retry and hedging are nested
(retry around hedging, retry inside hedged branches), so a number is never repeated within an execution.
Strategies' own counters (RetryPredicateArgs.attemptNumber, OnHedgingArgs.attemptNumber) stay local.
attemptNumber));
returnnew
var Request:new (input:RequestInfo|URL, init?:RequestInit) =>Request
The Request interface of the Fetch API represents a resource request.
init.signal cancels the whole call, retries included. The call rejects with the signal’s reason.
Each attempt gets its own signal, aborted by the attempt timeout, the total timeout or yours.
Called inside another pipeline (an inbound request deadline, for example), the call inherits that pipeline’s signal: when the outer budget runs out, the request is aborted. See deadline propagation.
standardHttpPipeline(options) builds the same pipeline as a plain ResiliencePipeline<Response>, for when you want to call fetch yourself or add strategies around it. isTransientHttpOutcome, parseRetryAfter and retryAfterMs are exported too, for your own predicates.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
A drop-in fetch with timeouts, retries and a circuit breaker per host:
const http = createResilientFetch({ name: 'payments' }); const res = await http(url, { resilience: { retries: 1 } });
Responses are returned as-is (a final 503 resolves, like fetch); only network errors and resilience rejections throw.
Polly-style builder with a shortcut for every strategy: addRetry(), addTimeout(), addCircuitBreaker(), …
Convenient, but it bundles every strategy. For the smallest browser bundle use PipelineBuilder with
retryStrategy(), timeoutStrategy(), … instead; the two are interchangeable.
The type parameters track which errors the pipeline can raise, for pipeline.settle() + Outcome.match().
ResiliencePipelineBuilder<
interfaceResponse
The Response interface of the Fetch API represents the response to a request.
A drop-in fetch with timeouts, retries and a circuit breaker per host:
const http = createResilientFetch({ name: 'payments' }); const res = await http(url, { resilience: { retries: 1 } });
Responses are returned as-is (a final 503 resolves, like fetch); only network errors and resilience rejections throw.