Skip to content

Context, signals and deadlines

Every callback receives a ResilienceContext. Its most important field is signal: timeouts, hedging and caller cancellation all work by aborting it. JavaScript can’t stop a running promise, so pass signal to your I/O.

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

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
,
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
}) =>
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
('https://api.example.com/report', {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
,
RequestInit.headers?: HeadersInit | undefined

A Headers object, an object literal, or an array of two-item arrays to set request's headers.

headers
: { 'x-attempt':
var String: StringConstructor
(value?: any) => string

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
) },
}),
);
Field Type Description
signal AbortSignal Aborted when the caller cancels, an outer pipeline gives up, or a strategy (timeout, hedging) abandons this attempt
attemptNumber number 0 for the first attempt, then 1, 2… for retries and hedged attempts, in the order they start. Unique within an execution, however retry and hedging are nested
operationKey string | undefined From execute(fn, { operationKey }); used by filters such as setGlobalChaos({ filter })
properties Map<string, unknown> A bag shared by the strategies and the callback for this execution
idempotent boolean false forbids repeating calls that may have started (default true)
operationName string? Telemetry name for this execution (execute(fn, { name }))
attributes Record<string, string | number | boolean>? Extra telemetry attributes for this execution
timeProvider TimeProvider? Clock for this execution’s delays and timeouts

All fields are optional: pipeline.execute(callback, options), and the same for executeOutcome, settle, executeStream and wrap.

Option Default Effect
signal none Cancels the execution. Combined with the signal of the execution this one runs inside, if any
operationKey none Copied to context.operationKey
properties new Map Initial context.properties
idempotent true See Calls that must not run twice
abortOnReturn false Abort each attempt’s signal as soon as its callback settles
name pipeline name Operation name for spans and metrics
attributes none Extra low-cardinality attributes for spans and OpenTelemetry metrics
timeProvider pipeline’s clock Clock for this run’s retry delays, timeouts, hedging delays and chaos latency

A per-execution timeProvider only affects timing that belongs to the execution. Breakers, limiters, caches and locks keep the pipeline’s clock, because their state spans executions.

A pipeline executed inside another pipeline’s callback inherits its signal. An outer timeout therefore cancels every call made underneath it, including calls that never received the signal:

const
const perRequest: ResiliencePipeline<any, TimeoutRejectedError>
perRequest
= 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>.addTimeout(options: Duration | TimeoutOptions): Next<any, never, never, TimeoutRejectedError>

addTimeout(5000), addTimeout('5s') or addTimeout({ timeout: '5s', onTimeout }).

addTimeout
('3s').
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.build(): ResiliencePipeline<any, TimeoutRejectedError>
build
();
const
const payments: ResiliencePipeline<any, never>
payments
= 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
({
ResiliencePipelineBuilderOptions.name?: string | undefined

Labels telemetry events.

name
: 'payments' }).
ResiliencePipelineBuilder<any, never, never>.addRetry(options?: RetryOptions<any>): ResiliencePipelineBuilder<any, never, never>
addRetry
().
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();
// Deep inside a service: no signal parameter anywhere.
async function
function chargeCustomer(id: string): Promise<Response>
chargeCustomer
(
id: string
id
: string) {
return
const payments: ResiliencePipeline<any, never>
payments
.
ResiliencePipeline<any, never>.execute<Response>(callback: (context: ResilienceContext) => MaybePromise<Response>, options?: ExecuteOptions): Promise<Response>

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 fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
(`https://pay.internal/charge/${
id: string
id
}`, {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
}));
}
// At the edge, e.g. an HTTP handler:
await
const perRequest: ResiliencePipeline<any, TimeoutRejectedError>
perRequest
.
ResiliencePipeline<any, TimeoutRejectedError>.execute<Response>(callback: (context: ResilienceContext) => MaybePromise<Response>, options?: ExecuteOptions): Promise<Response>

Runs the callback through the pipeline; throws the final error.

execute
(() =>
function chargeCustomer(id: string): Promise<Response>
chargeCustomer
('c_42')); // the fetch is aborted when the 3 s budget runs out

The nested signal aborts when either its own strategies or any outer execution give up. This works through AsyncLocalStorage, so it needs a runtime that has it: Node, Bun, Deno, and Workers with nodejs_compat. In browsers, pass the signal explicitly (see Runtimes).

Code that runs inside an execution but doesn’t receive the context (a decorated method, a repository, a database helper) can read it:

export async function
function findOrders(customerId: string): Promise<unknown[]>
findOrders
(
customerId: string
customerId
: string) {
const
const attempt: number
attempt
=
function getResilienceContext(): ResilienceContext | undefined

Context of the resilient execution the calling code runs in (works in nested services), or undefined.

getResilienceContext
()?.
ResilienceContext.attemptNumber: number | undefined

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
?? 0;
return
const db: {
query(sql: string, options: {
signal?: AbortSignal;
}): Promise<unknown[]>;
}
db
.
function query(sql: string, options: {
signal?: AbortSignal;
}): Promise<unknown[]>
query
(`select * from orders where customer_id = $1 -- attempt ${
const attempt: number
attempt
}`, {
signal?: AbortSignal | undefined
signal
:
function getResilienceSignal(): AbortSignal | undefined

Shortcut for getResilienceContext()?.signal: pass it to fetch / your DB driver from deep inside a call.

getResilienceSignal
(), // undefined outside an execution
});
}

Both return undefined outside an execution, and always in browsers.

Mark non-idempotent calls, such as a payment, with idempotent: false:

await
const pipeline: ResiliencePipeline<any, BulkheadRejectedError>
pipeline
.
ResiliencePipeline<any, BulkheadRejectedError>.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 createPayment(signal: AbortSignal): Promise<{
id: string;
}>
createPayment
(
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 });

Retry and hedging then only repeat the call when it never started, for example because a limiter rejected it. A failure that may have reached the dependency (a timeout, a 503, a dropped connection) is returned as is. Hedging never launches a parallel attempt while one may be in flight.

Some callbacks start work that outlives them: a timer, a stream the caller only partly reads, a background refresh. abortOnReturn: true aborts the attempt’s signal as soon as the callback settles, so that work stops too.

context.properties is an untyped Map. ResiliencePropertyKey gives typed access, in the style of Polly:

const
const TENANT: ResiliencePropertyKey<string>
TENANT
= new
new ResiliencePropertyKey<string>(key: string): ResiliencePropertyKey<string>

A typed key for context.properties (Polly's ResiliencePropertyKey): const TENANT = new ResiliencePropertyKey<string>('tenant').

ResiliencePropertyKey
<string>('tenant');
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>.onRetry?: ((args: OnRetryArgs<any>) => MaybePromise<void>) | undefined

Before each retry's delay.

onRetry
: ({
context: ResilienceContext
context
}) =>
var console: Console

The console module provides a simple debugging console that is similar to the JavaScript console mechanism provided by web browsers.

The module exports two specific components:

  • A Console class with methods such as console.log(), console.error() and console.warn() that can be used to write to any Node.js stream.
  • A global console instance configured to write to process.stdout and process.stderr. The global console can be used without importing the node:console module.

Warning: The global console object's methods are neither consistently synchronous like the browser APIs they resemble, nor are they consistently asynchronous like all other Node.js streams. See the note on process I/O for more information.

Example using the global console:

console.log('hello world');
// Prints: hello world, to stdout
console.log('hello %s', 'world');
// Prints: hello world, to stdout
console.error(new Error('Whoops, something bad happened'));
// Prints error message and stack trace to stderr:
// Error: Whoops, something bad happened
// at [eval]:5:15
// at Script.runInThisContext (node:vm:132:18)
// at Object.runInThisContext (node:vm:309:38)
// at node:internal/process/execution:77:19
// at [eval]-wrapper:6:22
// at evalScript (node:internal/process/execution:76:60)
// at node:internal/main/eval_string:23:3
const name = 'Will Robinson';
console.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to stderr

Example using the Console class:

const out = getStreamSomehow();
const err = getStreamSomehow();
const myConsole = new console.Console(out, err);
myConsole.log('hello world');
// Prints: hello world, to out
myConsole.log('hello %s', 'world');
// Prints: hello world, to out
myConsole.error(new Error('Whoops, something bad happened'));
// Prints: [Error: Whoops, something bad happened], to err
const name = 'Will Robinson';
myConsole.warn(`Danger ${name}! Danger!`);
// Prints: Danger Will Robinson! Danger!, to err

@see ― source

console
.
Console.log(message?: any, ...optionalParams: any[]): void (+1 overload)

Prints to stdout with newline. Multiple arguments can be passed, with the first used as the primary message and all additional used as substitution values similar to printf(3) (the arguments are all passed to util.format()).

const count = 5;
console.log('count: %d', count);
// Prints: count: 5, to stdout
console.log('count:', count);
// Prints: count: 5, to stdout

See util.format() for more information.

@since ― v0.1.100

log
('retrying for',
const TENANT: ResiliencePropertyKey<string>
TENANT
.
ResiliencePropertyKey<string>.get(context: {
readonly properties: Map<string, unknown>;
}): string | undefined
get
(
context: ResilienceContext
context
)),
})
.
ResiliencePipelineBuilder<any, never, never>.build(): ResiliencePipeline<any, never>
build
();
await
const pipeline: ResiliencePipeline<any, never>
pipeline
.
ResiliencePipeline<any, never>.execute<Response>(callback: (context: ResilienceContext) => MaybePromise<Response>, options?: ExecuteOptions): Promise<Response>

Runs the callback through the pipeline; throws the final error.

execute
(
(
context: ResilienceContext
context
) =>
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
(`https://api.example.com/${
const TENANT: ResiliencePropertyKey<string>
TENANT
.
ResiliencePropertyKey<string>.get(context: {
readonly properties: Map<string, unknown>;
}): string | undefined
get
(
context: ResilienceContext
context
)}`, {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
:
context: ResilienceContext
context
.
ResilienceContext.signal: AbortSignal

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

signal
}),
{
ExecuteOptions.properties?: Map<string, unknown> | undefined
properties
: new
var Map: MapConstructor
new <string, string>(iterable?: Iterable<readonly [string, string]> | null | undefined) => Map<string, string> (+3 overloads)
Map
([[
const TENANT: ResiliencePropertyKey<string>
TENANT
.
ResiliencePropertyKey<string>.key: string
key
, 'acme']]) },
);