Skip to content

Retry

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.

import {
class ResiliencePipelineBuilder<T = any, E = never, C extends string = never>

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
} from 'resilience-pipeline';
const
const pipeline: ResiliencePipeline<any, never>
pipeline
= new
new ResiliencePipelineBuilder<any, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<any, never, never>

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
()
.
ResiliencePipelineBuilder<any, never, never>.addRetry(options?: RetryOptions<any>): ResiliencePipelineBuilder<any, never, never>
addRetry
({
RetryOptions<any>.maxRetryAttempts?: number | undefined

Retries in addition to the original call. Default 3. Infinity retries until aborted.

maxRetryAttempts
: 3, // 4 calls at most
RetryOptions<any>.backoffType?: BackoffType | undefined

Default 'constant'.

backoffType
: 'exponential', // 200 ms, 400 ms, 800 ms
RetryOptions<any>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: '200ms',
RetryOptions<any>.useJitter?: Jitter | undefined
useJitter
: 'full',
})
.
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();
const
const user: {
id: string;
}
user
= await
const pipeline: ResiliencePipeline<any, never>
pipeline
.
ResiliencePipeline<any, never>.execute<{
id: string;
}>(callback: (context: ResilienceContext) => MaybePromise<{
id: string;
}>, options?: ExecuteOptions): Promise<{
id: string;
}>

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
}) =>
function fetchUser(id: string, signal: AbortSignal): Promise<{
id: string;
}>
fetchUser
('42',
signal: AbortSignal

Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.

signal
));
  1. 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.
  2. 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.
  3. onFailedAttempt runs. If no retries are left, onRetryExhausted runs and the last outcome is returned.
  4. The delay is computed: delayGenerator, else the backoff formula, then raised to the error’s retryAfter if that is longer.
  5. If waiting would pass maxRetryTime, retry gives up (onRetryExhausted). If a budget refuses the retry, it gives up too (onBudgetExhausted).
  6. 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.

The base delay for the retry after attempt n (0 = the original call) is:

backoffType Delay delay: 200 → retries 1, 2, 3, 4
'constant' (default) delay 200, 200, 200, 200
'linear' delay × (n + 1) 200, 400, 600, 800
'exponential' delay × factor^n 200, 400, 800, 1600 (factor: 2)

maxDelay caps that value before jitter is applied, so 'full' and 'equal' spread over the capped value.

0 ms2.5 s5 s7.5 s10 s123456
Bars: delay before retry 1…6.

Total wait: 12.6 s

retryStrategy({ maxRetryAttempts: 6, backoffType: 'exponential', delay: 200 })
Delay before each retry
RetryDelay
1200 ms
2400 ms
3800 ms
41.6 s
53.2 s
66.4 s

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:

import {
class ResiliencePipelineBuilder<T = any, E = never, C extends string = never>

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
,
const backoff: {
constant: (options: BackoffOptions) => (attempt: Attempt) => number;
linear: (options: BackoffOptions) => (attempt: Attempt) => number;
exponential: (options: BackoffOptions & {
factor?: number;
}) => (attempt: Attempt) => number;
sequence: (delays: readonly Duration[]) => (attempt: Attempt) => number;
}

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 'resilience-pipeline';
// Exponential with a custom factor, cap and full jitter.
const
const exp: (attempt: Attempt) => number
exp
=
const backoff: {
constant: (options: BackoffOptions) => (attempt: Attempt) => number;
linear: (options: BackoffOptions) => (attempt: Attempt) => number;
exponential: (options: BackoffOptions & {
factor?: number;
}) => (attempt: Attempt) => number;
sequence: (delays: readonly Duration[]) => (attempt: Attempt) => number;
}

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
.
exponential: (options: BackoffOptions & {
factor?: number;
}) => (attempt: Attempt) => number
exponential
({
BackoffOptions.delay: Duration

Base delay.

delay
: '100ms',
factor?: number | undefined
factor
: 3,
BackoffOptions.maxDelay?: Duration | undefined

Cap for a single delay.

maxDelay
: '10s',
BackoffOptions.useJitter?: false | "full" | "equal" | undefined

Same as retry's useJitter, minus true (Polly's decorrelated jitter needs state across attempts). Default false.

useJitter
: 'full' });
// A fixed schedule; the last delay repeats: 100 ms, 1 s, 5 s, 5 s, …
const
const steps: (attempt: Attempt) => number
steps
=
const backoff: {
constant: (options: BackoffOptions) => (attempt: Attempt) => number;
linear: (options: BackoffOptions) => (attempt: Attempt) => number;
exponential: (options: BackoffOptions & {
factor?: number;
}) => (attempt: Attempt) => number;
sequence: (delays: readonly Duration[]) => (attempt: Attempt) => number;
}

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: readonly Duration[]) => (attempt: Attempt) => number

A fixed list of delays (cockatiel's IterableBackoff); the last one repeats: backoff.sequence(['100ms', '1s', '5s']).

sequence
(['100ms', '1s', '5s']);
const exp: (attempt: Attempt) => number
exp
(0); // 0..100
const steps: (attempt: Attempt) => number
steps
(4); // 5000
const
const pipeline: ResiliencePipeline<any, never>
pipeline
= new
new ResiliencePipelineBuilder<any, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<any, never, never>

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
()
.
ResiliencePipelineBuilder<any, never, never>.addRetry(options?: RetryOptions<any>): ResiliencePipelineBuilder<any, never, never>
addRetry
({
RetryOptions<any>.maxRetryAttempts?: number | undefined

Retries in addition to the original call. Default 3. Infinity retries until aborted.

maxRetryAttempts
: 5,
RetryOptions<any>.delayGenerator?: ((args: RetryPredicateArgs<any>) => MaybePromise<Duration | undefined>) | undefined

Return a delay (e.g. from a Retry-After header); undefined or negative falls back to the computed delay.

delayGenerator
:
const steps: (attempt: Attempt) => number
steps
})
.
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();
Helper Options
backoff.constant(o) delay (required), maxDelay, useJitter: 'full' | 'equal' | false, randomizer
backoff.linear(o) same as constant
backoff.exponential(o) same, plus factor (default 2)
backoff.sequence(delays) a non-empty list of durations; the last one repeats

Each returned function accepts a number or anything with an attemptNumber (such as the retry predicate args).

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:

import {
class ResiliencePipelineBuilder<T = any, E = never, C extends string = never>

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
,
class PredicateBuilder<T = any>

Builds a shouldHandle predicate: new PredicateBuilder().handle(HttpError, e => e.status >= 500).handleResult(r => r.status === 429).

PredicateBuilder
} from 'resilience-pipeline';
import {
function retryAfterMs(response: Response, now?: number): number | undefined

Delay from a response's Retry-After header, in ms; undefined when absent or unparseable.

retryAfterMs
} from 'resilience-pipeline/fetch';
const
const pipeline: ResiliencePipeline<Response, never>
pipeline
= new
new ResiliencePipelineBuilder<Response, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<Response, never, never>

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
<
interface Response

The Response interface of the Fetch API represents the response to a request.

MDN Reference

Response
>()
.
ResiliencePipelineBuilder<Response, never, never>.addRetry(options?: RetryOptions<Response>): ResiliencePipelineBuilder<Response, never, never>
addRetry
({
RetryOptions<Response>.maxRetryAttempts?: number | undefined

Retries in addition to the original call. Default 3. Infinity retries until aborted.

maxRetryAttempts
: 4,
RetryOptions<Response>.backoffType?: BackoffType | undefined

Default 'constant'.

backoffType
: 'exponential',
RetryOptions<Response>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: '500ms',
RetryOptions<Response>.shouldHandle?: ShouldHandle<RetryPredicateArgs<Response>> | undefined

Default: transient errors (not 4xx except 408/429, not aborts, not rejections). An open circuit is never retried.

shouldHandle
: new
new PredicateBuilder<Response>(): PredicateBuilder<Response>

Builds a shouldHandle predicate: new PredicateBuilder().handle(HttpError, e => e.status >= 500).handleResult(r => r.status === 429).

PredicateBuilder
<
interface Response

The Response interface of the Fetch API represents the response to a request.

MDN Reference

Response
>()
.
PredicateBuilder<Response>.handle<Error>(type: ErrorClass<Error>, predicate?: (error: Error) => boolean): PredicateBuilder<Response>
handle
(
var TypeError: TypeErrorConstructor
TypeError
)
.
PredicateBuilder<Response>.handleResult(predicate: (result: Response) => boolean): PredicateBuilder<Response>
handleResult
((
r: Response
r
) =>
r: Response
r
.
Response.status: number

The status read-only property of the Response interface contains the HTTP status codes of the response.

MDN Reference

status
=== 429 ||
r: Response
r
.
Response.status: number

The status read-only property of the Response interface contains the HTTP status codes of the response.

MDN Reference

status
=== 503),
// Honour Retry-After on 429/503 responses; undefined falls back to the backoff.
RetryOptions<Response>.delayGenerator?: ((args: RetryPredicateArgs<Response>) => MaybePromise<Duration | undefined>) | undefined

Return a delay (e.g. from a Retry-After header); undefined or negative falls back to the computed delay.

delayGenerator
: ({
outcome: Outcome<Response, unknown>
outcome
}) => (
outcome: Outcome<Response, unknown>
outcome
.
ok: boolean
ok
?
function retryAfterMs(response: Response, now?: number): number | undefined

Delay from a response's Retry-After header, in ms; undefined when absent or unparseable.

retryAfterMs
(
outcome: {
readonly ok: true;
readonly result: Response;
}
outcome
.
result: Response
result
) :
var undefined
undefined
),
})
.
ResiliencePipelineBuilder<Response, never, never>.build(): ResiliencePipeline<Response, never>
build
();

createResilientFetch() does this for you.

Option Type Default Description
name string 'Retry' Strategy name in telemetry and getStrategy().
shouldHandle (args) => boolean | Promise<boolean> or PredicateBuilder transient errors Which outcomes are retried. Default: errors except 4xx (408/429 are retried), aborts and rejections. Results are never retried by default.
maxRetryAttempts number 3 Retries after the original call. 0 disables retrying; Infinity retries until aborted.
delay Duration 2000 Base delay.
backoffType 'constant' | 'linear' | 'exponential' 'constant' How the delay grows.
factor number 2 Growth per retry for 'exponential'. Must be ≥ 1.
useJitter boolean | 'full' | 'equal' false See Jitter.
maxDelay Duration none Cap for computed delays and for retryAfter. Does not cap delayGenerator results.
delayGenerator (args) => Duration | undefined (or a promise) none Per-attempt delay. undefined or negative uses the computed delay.
respectRetryAfter boolean true Wait at least the error’s numeric retryAfter (ms).
initialDelay Duration 0 Wait before the first attempt too, for example to let a just-started dependency come up.
maxRetryTime Duration none Stop scheduling retries once the next one would start more than this long after the first attempt. The attempt in flight is not cancelled.
shouldConsumeRetry (args) => boolean (or a promise) always true Return false for failures that should be retried without using up a retry (a token refresh).
onRetry (args: OnRetryArgs) => void none Before each retry’s delay. Gets retryDelay and the failed attempt’s duration.
onFailedAttempt (args: OnFailedAttemptArgs) => void none After every handled failure, the last one included. Gets retriesLeft and duration.
onRetryExhausted (args) => void none When retries or maxRetryTime run out and the last outcome is still a failure.
randomizer () => number Math.random Random source in [0, 1) for jitter.
budget RetryBudget none Shared retry budget, see below.
onBudgetExhausted (args) => void none 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:

import {
function retry<T>(fn: (context: ResilienceContext) => MaybePromise<T>, options?: RetryOptions<T> & CallOptions): Promise<T>

One-off retry, no pipeline needed: await retry(() => fetchUser(id), { maxRetryAttempts: 5, backoffType: 'exponential' }). Throw NonRetryableError from fn to stop early.

retry
,
class NonRetryableError

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
} from 'resilience-pipeline';
const
const res: Response
res
= await
retry<Response>(fn: (context: ResilienceContext) => MaybePromise<Response>, options?: RetryOptions<Response> & CallOptions): Promise<Response>

One-off retry, no pipeline needed: await retry(() => fetchUser(id), { maxRetryAttempts: 5, backoffType: 'exponential' }). Throw NonRetryableError from fn to stop early.

retry
(async ({
signal: AbortSignal

Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.

signal
}) => {
const
const res: Response
res
= await
function charge(amount: number, signal: AbortSignal): Promise<Response>
charge
(100,
signal: AbortSignal

Aborted when the caller cancels, or when a strategy (timeout, hedging) gives up on this execution.

signal
);
if (
const res: Response
res
.
Response.status: number

The status read-only property of the Response interface contains the HTTP status codes of the response.

MDN Reference

status
=== 402) throw new
new NonRetryableError(errorOrMessage: unknown): NonRetryableError

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 (!
const res: 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.

MDN Reference

ok
) throw new
var Error: ErrorConstructor
new (message?: string, options?: ErrorOptions) => Error (+1 overload)
Error
(`HTTP ${
const res: Response
res
.
Response.status: number

The status read-only property of the Response interface contains the HTTP status codes of the response.

MDN Reference

status
}`);
return
const res: Response
res
;
}, {
RetryOptions<T>.maxRetryAttempts?: number | undefined

Retries in addition to the original call. Default 3. Infinity retries until aborted.

maxRetryAttempts
: 3 });

It accepts an error or a message: new NonRetryableError('invalid card').

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.

await
const pipeline: ResiliencePipeline<any, never>
pipeline
.
ResiliencePipeline<any, never>.execute<void>(callback: (context: ResilienceContext) => MaybePromise<void>, options?: ExecuteOptions): Promise<void>

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
}) =>
function pay(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.

import {
class ResiliencePipelineBuilder<T = any, E = never, C extends string = never>

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
,
class RetryBudget

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
} from 'resilience-pipeline';
const
const inventoryBudget: RetryBudget
inventoryBudget
= new
new RetryBudget(options?: RetryBudgetOptions): RetryBudget

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.

minRetriesPerSecond
: 10 }); // at most +20% load
const
const reads: ResiliencePipeline<any, never>
reads
= new
new ResiliencePipelineBuilder<any, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<any, never, never>

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
().
ResiliencePipelineBuilder<any, never, never>.addRetry(options?: RetryOptions<any>): ResiliencePipelineBuilder<any, never, never>
addRetry
({
RetryOptions<any>.budget?: RetryBudget | undefined

Shared cap on retries relative to traffic (retry-storm protection). Share one instance per dependency.

budget
:
const inventoryBudget: RetryBudget
inventoryBudget
}).
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();
const
const writes: ResiliencePipeline<any, never>
writes
= new
new ResiliencePipelineBuilder<any, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<any, never, never>

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
().
ResiliencePipelineBuilder<any, never, never>.addRetry(options?: RetryOptions<any>): ResiliencePipelineBuilder<any, never, never>
addRetry
({
RetryOptions<any>.budget?: RetryBudget | undefined

Shared cap on retries relative to traffic (retry-storm protection). Share one instance per dependency.

budget
:
const inventoryBudget: RetryBudget
inventoryBudget
,
RetryOptions<any>.maxRetryAttempts?: number | undefined

Retries in addition to the original call. Default 3. Infinity retries until aborted.

maxRetryAttempts
: 1 }).
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();
const inventoryBudget: RetryBudget
inventoryBudget
.
RetryBudget.stats(): {
requests: number;
retries: number;
}

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.

Telemetry event Callback When
OnRetry onRetry Before each retry’s delay.
OnRetryExhausted onRetryExhausted Retries or maxRetryTime ran out.
OnRetryBudgetExhausted onBudgetExhausted The budget refused a retry.
OnExecutionAttempt none Every attempt, emitted by the pipeline (also without retry). Args include attemptNumber and duration.

onFailedAttempt has no telemetry event. See the events catalog.

Retry raises no error of its own. The caller gets the last attempt’s outcome. If an abort cancels a wait, the abort reason is returned.

  • 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.