Retry runs the call again when it fails with a transient error: a dropped connection, a 503, a timeout. It waits between attempts (backoff) so the dependency gets time to recover.
Don’t retry what can’t succeed the second time: validation errors, 4xx responses, an open circuit. The default predicate already skips those. Don’t retry calls that must not run twice (payments) unless the dependency is idempotent; mark them with idempotent: false.
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().
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 adds Polly-style addRetry() / addTimeout() / … shortcuts on top, at the cost of
bundling every strategy. Strategies run in the order they are added: the first one added is the outermost.
Options are validated and strategy state (e.g. circuit breaker) is created on build().
ResiliencePipelineBuilder adds Polly-style addRetry() / addTimeout() / … shortcuts on top, at the cost of
bundling every strategy. Strategies run in the order they are added: the first one added is the outermost.
Options are validated and strategy state (e.g. circuit breaker) is created on build().
retry(fn, options) builds a one-strategy pipeline and calls it. It also accepts signal and timeProvider next to the retry options. It is the smallest entry point (about 4.1 KB min+gzip).
The callback runs. context.attemptNumber is 0 for the original call, then 1, 2, … for retries (one counter per execution, shared with hedging, so numbers never repeat). With executeStream, a successful stream that shouldHandle retries is closed first.
Retry stops at once and returns the outcome if any of these hold:
the outcome is not handled by shouldHandle (by default: successes, 4xx except 408/429, aborts, rejections from other strategies);
the error is a BrokenCircuitError (open or isolated circuit), whatever shouldHandle says;
the error is a NonRetryableError;
the execution’s signal is aborted;
the call is marked idempotent: false and the failure was not a rejection.
onFailedAttempt runs. If no retries are left, onRetryExhausted runs and the last outcome is returned.
The delay is computed: delayGenerator, else the backoff formula, then raised to the error’s retryAfter if that is longer.
If waiting would pass maxRetryTime, retry gives up (onRetryExhausted). If a budget refuses the retry, it gives up too (onBudgetExhausted).
onRetry runs, the delay elapses (an abort cancels the wait), and the next attempt starts.
When all retries fail, the caller gets the last attempt’s error, not a wrapper.
When many clients fail at the same moment, identical delays make them all retry at the same moment too. Jitter spreads them out. d is the base delay after the maxDelay cap, r a random number in [0, 1):
useJitter
Delay
Use it when
false (default)
d
One caller, or tests.
'full'
r × d (0 to d)
Many callers hit one dependency. Spreads load best.
'equal'
d/2 + r × d/2 (d/2 to d)
You want spread but also a minimum wait.
true, constant / linear
d ± 25%, capped at maxDelay
Polly compatibility.
true, exponential
Polly’s decorrelated jitter V2, capped at maxDelay
Polly compatibility. Grows roughly ×2 per attempt; factor is ignored.
Pass randomizer: () => number to make jitter deterministic in tests.
delayGenerator overrides the formula per attempt. Return undefined or a negative number to fall back to the computed delay. Generated delays are not capped by maxDelay.
The backoff helpers are plain functions of the attempt number. Use them as a delayGenerator, in waitFor({ interval }), or in your own loops:
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().
Delay schedules as plain functions of the attempt number (0-based), for delayGenerator, waitFor({ interval }),
or your own loops: addRetry({ delayGenerator: backoff.exponential({ delay: '100ms', maxDelay: '10s', useJitter: 'full' }) }).
backoff } from'@nodelibraries/resilience';
// Exponential with a custom factor, cap and full jitter.
Delay schedules as plain functions of the attempt number (0-based), for delayGenerator, waitFor({ interval }),
or your own loops: addRetry({ delayGenerator: backoff.exponential({ delay: '100ms', maxDelay: '10s', useJitter: 'full' }) }).
Delay schedules as plain functions of the attempt number (0-based), for delayGenerator, waitFor({ interval }),
or your own loops: addRetry({ delayGenerator: backoff.exponential({ delay: '100ms', maxDelay: '10s', useJitter: 'full' }) }).
backoff.
sequence: (delays:readonlyDuration[]) => (attempt:Attempt) => number
A fixed list of delays (cockatiel's IterableBackoff); the last one repeats: backoff.sequence(['100ms', '1s', '5s']).
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().
Errors can carry a retryAfter in milliseconds. RateLimiterRejectedError does. With respectRetryAfter: true (the default), retry waits at least that long, still capped by maxDelay. For HTTP responses, read the header in a delayGenerator:
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,
classPredicateBuilder<T=any>
Builds a shouldHandle predicate: new PredicateBuilder().handle(HttpError, e => e.status >= 500).handleResult(r => r.status === 429).
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.
The budget refused a retry. The last outcome is returned.
Duration is milliseconds or a string: '250ms', '30s', '5m', '1h', '1d', '2w'. All callbacks may be async; retry awaits them. Every callback gets outcome, context and attemptNumber (of the attempt that failed).
Invalid values throw a RangeError when the pipeline is built: negative maxRetryAttempts, delay, maxDelay or initialDelay, factor < 1, maxRetryTime <= 0.
Throw NonRetryableError from the callback when you know retrying is pointless. Retry stops, and the caller receives the original error, not the wrapper:
One-off retry, no pipeline needed: await retry(() => fetchUser(id), { maxRetryAttempts: 5, backoffType: 'exponential' }).
Throw NonRetryableError from fn to stop early.
retry,
classNonRetryableError
Throw (or wrap an error in) this to stop retrying right away: retry gives up and the caller receives the original error.
Like p-retry's AbortError. throw new NonRetryableError(error) or throw new NonRetryableError('invalid card').
Throw (or wrap an error in) this to stop retrying right away: retry gives up and the caller receives the original error.
Like p-retry's AbortError. throw new NonRetryableError(error) or throw new NonRetryableError('invalid card').
NonRetryableError(new
var Error:ErrorConstructor
new (message?:string, options?:ErrorOptions) =>Error (+1 overload)
Error('card declined'));
if (!
constres:Response
res.
Response.ok: boolean
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 BrokenCircuitError (CIRCUIT_OPEN or CIRCUIT_ISOLATED) always stops retry, even if your shouldHandle would match it. The circuit is open to protect a failing dependency; retrying only adds load. Put the breaker inside the retry (add retry first) so each attempt goes through it.
Runs the callback through the pipeline; throws the final error.
execute(({
signal: AbortSignal
Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.
signal }) =>
functionpay(signal:AbortSignal):Promise<void>
pay(
signal: AbortSignal
Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.
signal), {
ExecuteOptions.idempotent?: boolean |undefined
Default true. false: never repeat a call that may have reached the dependency (no retries/hedging of started calls).
idempotent: false });
With idempotent: false, retry only repeats a call that never started: one that failed with a rejection (an open circuit or full limiter further in). A call that may have reached the dependency is never repeated. The default shouldHandle does not handle rejections, so in practice such a call is not retried at all unless your predicate handles, say, isRateLimitError.
Each caller retrying 3 times means up to 4× the load, exactly when the dependency is weakest. A RetryBudget caps retries relative to traffic. Share one instance across every pipeline that calls the same dependency.
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,
classRetryBudget
Caps retries relative to traffic, so a failing dependency gets at most ratio extra load instead of a retry storm
(every caller retrying 3x = 4x load exactly when the dependency is weakest). Share one budget across every pipeline
that calls the same dependency: addRetry({ budget }).
Caps retries relative to traffic, so a failing dependency gets at most ratio extra load instead of a retry storm
(every caller retrying 3x = 4x load exactly when the dependency is weakest). Share one budget across every pipeline
that calls the same dependency: addRetry({ budget }).
RetryBudget({
RetryBudgetOptions.ratio?: number |undefined
Retries allowed as a fraction of original requests in the window. Default 0.2 (at most +20% load from retries).
ratio: 0.2,
RetryBudgetOptions.minRetriesPerSecond?: number |undefined
Retries always allowed per second, so low-traffic callers can still retry. Default 10.
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().
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().
Requests and retries counted in the current window.
stats(); // { requests, retries } in the current window
A retry is allowed while retries < minRetriesPerSecond × window_in_seconds + ratio × requests, counted over the window. With the defaults that is 100 + 20% of requests per 10 s. Each execution counts as one request (after initialDelay), each retry as one retry.
Option
Type
Default
Description
ratio
number
0.2
Retries allowed as a fraction of requests in the window.
minRetriesPerSecond
number
10
Retries always allowed per second, so low-traffic callers can still retry.
window
Duration
'10s'
How long counts are kept (10 buckets).
timeProvider
TimeProvider
system clock
Clock, for tests.
When the budget refuses, retry returns the last outcome, reports OnRetryBudgetExhausted and calls onBudgetExhausted. onRetryExhausted is not called.
Strategy order.addRetry().addTimeout('2s') gives each attempt 2 s. addTimeout('2s').addRetry() gives all attempts together 2 s. See Timeout.
shouldConsumeRetry with no limit. A failure that never consumes a retry is retried forever. Combine it with maxRetryTime or make sure the condition clears (the token refresh succeeds).
Long waits in tests. Use FakeTimeProvider instead of real delays.