Skip to content
Buy me a coffee

Quick start

Four steps, each building on the last. Every snippet on this page is type-checked against the library.

No pipeline needed for a single retry loop:

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
,
function withTimeout<T>(fn: (context: ResilienceContext) => MaybePromise<T>, timeout: Duration | TimeoutOptions, options?: CallOptions): Promise<T>

One-off timeout: await withTimeout(({ signal }) => fetch(url, { signal }), '2s'). Rejects with TimeoutRejectedError.

withTimeout
,
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 '@nodelibraries/resilience';
const
const user: {
id: string;
name: string;
}
user
= await
retry<{
id: string;
name: string;
}>(fn: (context: ResilienceContext) => MaybePromise<{
id: string;
name: string;
}>, options?: RetryOptions<{
id: string;
name: string;
}> & CallOptions): Promise<{
id: string;
name: string;
}>

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

retry
(() =>
function getUser(id: string): Promise<{
id: string;
name: string;
}>
getUser
('42'), {
RetryOptions<T>.maxRetryAttempts?: number | undefined

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

maxRetryAttempts
: 5,
RetryOptions<T>.backoffType?: BackoffType | undefined

Default 'constant'.

backoffType
: 'exponential',
RetryOptions<{ id: string; name: string; }>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: '200ms',
RetryOptions<T>.useJitter?: Jitter | undefined
useJitter
: 'full',
});
const
const page: Response
page
= await
withTimeout<Response>(fn: (context: ResilienceContext) => MaybePromise<Response>, timeout: Duration | TimeoutOptions, options?: CallOptions): Promise<Response>

One-off timeout: await withTimeout(({ signal }) => fetch(url, { signal }), '2s'). Rejects with TimeoutRejectedError.

withTimeout
(({
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://example.com', {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
}), '2s');

Throw new NonRetryableError(err) inside the callback to stop retrying at once; the caller receives err. The defaults already skip what retrying can’t fix: 4xx client errors (except 408 and 429) and aborts. See What counts as a failure.

When several strategies must cooperate, build a pipeline once and reuse it. Reuse matters: the circuit breaker’s state lives in the pipeline.

orders-client.ts
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 '@nodelibraries/resilience';
class
class HttpError
HttpError
extends
var Error: ErrorConstructor
Error
{
constructor(readonly
HttpError.status: number
status
: number) {
super(`HTTP ${
status: number
status
}`);
}
}
const
const ordersApi: ResiliencePipeline<any, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
ordersApi
= 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
: 'orders-api' })
.
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,
RetryOptions<any>.backoffType?: BackoffType | undefined

Default 'constant'.

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

Base delay. Default 2000 ms.

delay
: '200ms',
RetryOptions<any>.useJitter?: Jitter | undefined
useJitter
: true })
.
ResiliencePipelineBuilder<any, never, never>.addCircuitBreaker(options?: CircuitBreakerOptions<any>): Next<any, never, never, BrokenCircuitError>
addCircuitBreaker
({
CircuitBreakerOptions<any>.failureRatio?: number | undefined

Failure ratio in the window that breaks the circuit. Default 0.1.

failureRatio
: 0.5,
CircuitBreakerOptions<any>.minimumThroughput?: number | undefined

Minimum recorded calls in the window before the ratio is considered. Default 100.

minimumThroughput
: 20,
CircuitBreakerOptions<any>.samplingDuration?: Duration | undefined

Time-based window: calls of the last samplingDuration. Default 30 s. Ignored when samplingCount is set.

samplingDuration
: '30s',
CircuitBreakerOptions<any>.breakDuration?: Duration | undefined

How long the circuit stays open. Default 5 s.

breakDuration
: '15s' })
.
ResiliencePipelineBuilder<any, BrokenCircuitError, never>.addTimeout(options: Duration | TimeoutOptions): Next<any, BrokenCircuitError, never, TimeoutRejectedError>

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

addTimeout
('2s')
.
ResiliencePipelineBuilder<any, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>, never>.build(): ResiliencePipeline<any, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
build
();
export async function
function getOrder(id: string): Promise<{
id: string;
total: number;
}>
getOrder
(
id: string
id
: string) {
return
const ordersApi: ResiliencePipeline<any, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>
ordersApi
.
ResiliencePipeline<any, WithErrors<BrokenCircuitError, never, TimeoutRejectedError>>.execute<{
id: string;
total: number;
}>(callback: (context: ResilienceContext) => MaybePromise<{
id: string;
total: number;
}>, options?: ExecuteOptions): Promise<{
id: string;
total: number;
}>

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

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

An AbortSignal to set request's signal.

signal
});
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
constructor HttpError(status: number): HttpError
HttpError
(
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
.
Body.json(): Promise<any>
json
() as
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<{
id: string
id
: string;
total: number
total
: number }>;
});
}

What this does:

  • Order matters. The first strategy added is the outermost. Retry wraps the breaker, which wraps the timeout, so each attempt gets 2 s and is counted by the breaker. Reversing the order changes the behaviour; see Pipelines and strategy order.
  • HttpError has a status. The default predicates read it: a 503 is retried and counted by the breaker, a 404 is neither.
  • signal is passed to fetch. The timeout works by aborting it. Without it, the request keeps running after the pipeline has given up.

execute() throws. settle() returns an outcome whose error type lists everything the pipeline can raise, and Outcome.match() requires a handler for each error code:

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
,
type Outcome<T, E = unknown> = {
readonly ok: true;
readonly result: T;
} | {
readonly ok: false;
readonly error: E;
}
const Outcome: {
fromResult: <T>(result: T) => Outcome<T, never>;
fromError: <T = never, E = unknown>(error: E) => Outcome<T, E>;
is: (value: unknown) => value is Outcome<unknown, unknown>;
from: (value: unknown) => Outcome<unknown, unknown> | undefined;
try: {
<R>(fn: () => R): TryResult<R, unknown>;
<R, E>(options: {
try: () => R;
catch: (error: unknown) => E;
}): TryResult<R, E>;
};
all<const O extends readonly Outcome<any, any>[]>(outcomes: O): Outcome<{ -readonly [K in keyof O]: OutcomeValue<O[K]>; }, OutcomeError<O[number]>>;
partition<O extends Outcome<any, any>>(outcomes: readonly O[]): {
values: OutcomeValue<O>[];
errors: OutcomeError<O>[];
};
unwrap: <T>(outcome: Outcome<T, unknown>) => T;
unwrapErr<E, C extends ErrorCodes<E> = ErrorCodes<...>>(outcome: Outcome<unknown, E>, code?: C | undefined): ErrorWithCode<E, C>;
match<T, E, H extends OutcomeHandlers<T, E, unknown>>(outcome: Outcome<T, E>, handlers: H): ReturnType<H[keyof H] & ((...args: any) => any)>;
}

The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing. E is the error type when it's known (see pipeline.settle()), unknown otherwise.

Outcome
} from '@nodelibraries/resilience';
class
class NotFound
NotFound
extends
var Error: ErrorConstructor
Error
{
readonly
NotFound.code: "NOT_FOUND"
code
= 'NOT_FOUND' as
type const = "NOT_FOUND"
const
;
}
const
const users: ResiliencePipeline<any, WithErrors<TimeoutRejectedError, never, BrokenCircuitError>>
users
= 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
('2s')
.
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.addCircuitBreaker(options?: CircuitBreakerOptions<any>): Next<any, TimeoutRejectedError, never, BrokenCircuitError>
addCircuitBreaker
({
CircuitBreakerOptions<any>.consecutiveFailures?: number | undefined

Break after this many failures in a row (a success resets the count). Simple and good for low traffic. When set without failureRatio, the ratio check is off and only this rule applies.

consecutiveFailures
: 5 })
.
ResiliencePipelineBuilder<any, WithErrors<TimeoutRejectedError, never, BrokenCircuitError>, never>.build(): ResiliencePipeline<any, WithErrors<TimeoutRejectedError, never, BrokenCircuitError>>
build
();
const
const outcome: Outcome<{
id: string;
}, NotFound | WithErrors<TimeoutRejectedError, never, BrokenCircuitError> | UnexpectedError>
outcome
= await
const users: ResiliencePipeline<any, WithErrors<TimeoutRejectedError, never, BrokenCircuitError>>
users
.
ResiliencePipeline<any, WithErrors<TimeoutRejectedError, never, BrokenCircuitError>>.settle<{
id: string;
}, NotFound>(callback: (context: ResilienceContext) => MaybePromise<{
id: string;
} | Outcome<{
id: string;
}, NotFound>>, options?: ExecuteOptions): Promise<Outcome<{
id: string;
}, NotFound | WithErrors<TimeoutRejectedError, never, BrokenCircuitError> | UnexpectedError>>

Like executeOutcome, but the error is typed: the errors this pipeline's strategies can raise (E), the errors your callback returns with Outcome.fromError(…) (E2), or UnexpectedError wrapping anything else (a thrown bug, a client error). Pair it with Outcome.match() for exhaustive handling:

const outcome = await pipeline.settle(async () => (user ? user : Outcome.fromError(new NotFound(id))));
Outcome.match(outcome, { ok: (u) => u, NOT_FOUND: () => null, TIMEOUT: () => cached, CIRCUIT_OPEN: () => cached, UNEXPECTED: (e) => { throw e } });

Returned errors count as failures for the strategies (they can be retried) unless your predicates say otherwise.

settle
(async ({
signal: AbortSignal

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

signal
}) =>
(await
function findUser(id: string, signal: AbortSignal): Promise<{
id: string;
} | undefined>
findUser
('42',
signal: AbortSignal

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

signal
)) ??
const Outcome: {
fromResult: <T>(result: T) => Outcome<T, never>;
fromError: <T = never, E = unknown>(error: E) => Outcome<T, E>;
is: (value: unknown) => value is Outcome<unknown, unknown>;
from: (value: unknown) => Outcome<unknown, unknown> | undefined;
try: {
<R>(fn: () => R): TryResult<R, unknown>;
<R, E>(options: {
try: () => R;
catch: (error: unknown) => E;
}): TryResult<R, E>;
};
all<const O extends readonly Outcome<any, any>[]>(outcomes: O): Outcome<{ -readonly [K in keyof O]: OutcomeValue<O[K]>; }, OutcomeError<O[number]>>;
partition<O extends Outcome<any, any>>(outcomes: readonly O[]): {
values: OutcomeValue<O>[];
errors: OutcomeError<O>[];
};
unwrap: <T>(outcome: Outcome<T, unknown>) => T;
unwrapErr<E, C extends ErrorCodes<E> = ErrorCodes<...>>(outcome: Outcome<unknown, E>, code?: C | undefined): ErrorWithCode<E, C>;
match<T, E, H extends OutcomeHandlers<T, E, unknown>>(outcome: Outcome<T, E>, handlers: H): ReturnType<H[keyof H] & ((...args: any) => any)>;
}

The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing. E is the error type when it's known (see pipeline.settle()), unknown otherwise.

Outcome
.
fromError: <never, NotFound>(error: NotFound) => Outcome<never, NotFound>
fromError
(new
constructor NotFound(message?: string, options?: ErrorOptions): NotFound (+1 overload)
NotFound
('user 42')),
);
const
const user: any
user
=
const Outcome: {
fromResult: <T>(result: T) => Outcome<T, never>;
fromError: <T = never, E = unknown>(error: E) => Outcome<T, E>;
is: (value: unknown) => value is Outcome<unknown, unknown>;
from: (value: unknown) => Outcome<unknown, unknown> | undefined;
try: {
<R>(fn: () => R): TryResult<R, unknown>;
<R, E>(options: {
try: () => R;
catch: (error: unknown) => E;
}): TryResult<R, E>;
};
all<const O extends readonly Outcome<any, any>[]>(outcomes: O): Outcome<{ -readonly [K in keyof O]: OutcomeValue<O[K]>; }, OutcomeError<O[number]>>;
partition<O extends Outcome<any, any>>(outcomes: readonly O[]): {
values: OutcomeValue<O>[];
errors: OutcomeError<O>[];
};
unwrap: <T>(outcome: Outcome<T, unknown>) => T;
unwrapErr<E, C extends ErrorCodes<E> = ErrorCodes<...>>(outcome: Outcome<unknown, E>, code?: C | undefined): ErrorWithCode<E, C>;
match<T, E, H extends OutcomeHandlers<T, E, unknown>>(outcome: Outcome<T, E>, handlers: H): ReturnType<H[keyof H] & ((...args: any) => any)>;
}

The result of an execution: either a value or an error. Strategies pass outcomes around instead of throwing. E is the error type when it's known (see pipeline.settle()), unknown otherwise.

Outcome
.
match<{
id: string;
}, NotFound | WithErrors<TimeoutRejectedError, never, BrokenCircuitError> | UnexpectedError, {
ok: (u: {
id: string;
}) => {
id: string;
};
NOT_FOUND: () => null;
TIMEOUT: () => {
id: string;
};
CIRCUIT_OPEN: () => {
id: string;
};
CIRCUIT_ISOLATED: () => {
id: string;
};
UNEXPECTED: (e: UnexpectedError) => never;
}>(outcome: Outcome<{
id: string;
}, NotFound | WithErrors<TimeoutRejectedError, never, BrokenCircuitError> | UnexpectedError>, handlers: {
ok: (u: {
id: string;
}) => {
id: string;
};
NOT_FOUND: () => null;
TIMEOUT: () => {
id: string;
};
CIRCUIT_OPEN: () => {
id: string;
};
CIRCUIT_ISOLATED: () => {
id: string;
};
UNEXPECTED: (e: UnexpectedError) => never;
}): any

Exhaustive handling by error code: Outcome.match(await pipeline.settle(cb), { ok: (v) => …, TIMEOUT: (e) => …, CIRCUIT_OPEN: (e) => …, UNEXPECTED: (e) => … }). Add a breaker and forget its handler: compile error.

match
(
const outcome: Outcome<{
id: string;
}, NotFound | WithErrors<TimeoutRejectedError, never, BrokenCircuitError> | UnexpectedError>
outcome
, {
ok: (u: {
id: string;
}) => {
id: string;
}
ok
: (
u: {
id: string;
}
u
) =>
u: {
id: string;
}
u
,
type NOT_FOUND: () => null
NOT_FOUND
: () => null,
type TIMEOUT: () => {
id: string;
}
TIMEOUT
: () =>
const cached: {
id: string;
}
cached
,
type CIRCUIT_OPEN: () => {
id: string;
}
CIRCUIT_OPEN
: () =>
const cached: {
id: string;
}
cached
,
type CIRCUIT_ISOLATED: () => {
id: string;
}
CIRCUIT_ISOLATED
: () =>
const cached: {
id: string;
}
cached
,
type UNEXPECTED: (e: UnexpectedError) => never
UNEXPECTED
: (
e: UnexpectedError
e
) => {
throw
e: UnexpectedError
e
.
Error.cause?: unknown
cause
;
},
});

Remove the TIMEOUT handler and the code no longer compiles. UNEXPECTED wraps anything the pipeline didn’t produce: a bug, a 4xx. More in Typed errors.

Every strategy reads time from a TimeProvider. Swap in FakeTimeProvider and move the clock by hand:

retry.test.ts
import
function assert(value: unknown, message?: string | Error): asserts value
assert
from 'node:assert/strict';
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 TimeoutRejectedError
TimeoutRejectedError
} from '@nodelibraries/resilience';
import {
class FakeTimeProvider

A clock you move by hand, so retries, timeouts and open circuits can be tested without waiting.

const time = new FakeTimeProvider();
const pipeline = new ResiliencePipelineBuilder({ timeProvider: time }).addRetry({ delay: '10s' }).addTimeout('1s').build();
// Attach the expectation first: the promise settles while runAll() fires timers.
const done = assert.rejects(pipeline.execute(() => neverResolves()), TimeoutRejectedError);
await time.runAll(); // 4 timeouts and 3 delays, instantly
await done;

FakeTimeProvider
} from '@nodelibraries/resilience/testing';
const
const time: FakeTimeProvider
time
= new
new FakeTimeProvider(start?: number | Date): FakeTimeProvider

A clock you move by hand, so retries, timeouts and open circuits can be tested without waiting.

const time = new FakeTimeProvider();
const pipeline = new ResiliencePipelineBuilder({ timeProvider: time }).addRetry({ delay: '10s' }).addTimeout('1s').build();
// Attach the expectation first: the promise settles while runAll() fires timers.
const done = assert.rejects(pipeline.execute(() => neverResolves()), TimeoutRejectedError);
await time.runAll(); // 4 timeouts and 3 delays, instantly
await done;

FakeTimeProvider
();
const
const pipeline: ResiliencePipeline<any, TimeoutRejectedError>
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
({
ResiliencePipelineBuilderOptions.timeProvider?: TimeProvider | undefined

Clock and timers for every strategy. Default: the system clock. Use FakeTimeProvider in tests.

timeProvider
:
const time: FakeTimeProvider
time
})
.
ResiliencePipelineBuilder<any, never, never>.addRetry(options?: RetryOptions<any>): ResiliencePipelineBuilder<any, never, never>
addRetry
({
RetryOptions<any>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: '10s',
RetryOptions<any>.maxRetryAttempts?: number | undefined

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

maxRetryAttempts
: 3 })
.
ResiliencePipelineBuilder<any, never, never>.addTimeout(options: Duration | TimeoutOptions): Next<any, never, never, TimeoutRejectedError>

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

addTimeout
('1s')
.
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.build(): ResiliencePipeline<any, TimeoutRejectedError>
build
();
const
const done: Promise<void>
done
=
function assert(value: unknown, message?: string | Error): asserts value
assert
.
strict.rejects(block: (() => Promise<unknown>) | Promise<unknown>, error: assert.AssertPredicate, message?: string | Error): Promise<void> (+1 overload)
export strict.rejects

Awaits the asyncFn promise or, if asyncFn is a function, immediately calls the function and awaits the returned promise to complete. It will then check that the promise is rejected.

If asyncFn is a function and it throws an error synchronously, assert.rejects() will return a rejected Promise with that error. If the function does not return a promise, assert.rejects() will return a rejected Promise with an ERR_INVALID_RETURN_VALUE error. In both cases the error handler is skipped.

Besides the async nature to await the completion behaves identically to

throws

.

If specified, error can be a Class, RegExp, a validation function, an object where each property will be tested for, or an instance of error where each property will be tested for including the non-enumerable message and name properties.

If specified, message will be the message provided by the `

AssertionError

if theasyncFn` fails to reject.

import assert from 'node:assert/strict';
await assert.rejects(
async () => {
throw new TypeError('Wrong value');
},
{
name: 'TypeError',
message: 'Wrong value',
},
);
import assert from 'node:assert/strict';
await assert.rejects(
async () => {
throw new TypeError('Wrong value');
},
(err) => {
assert.strictEqual(err.name, 'TypeError');
assert.strictEqual(err.message, 'Wrong value');
return true;
},
);
import assert from 'node:assert/strict';
assert.rejects(
Promise.reject(new Error('Wrong value')),
Error,
).then(() => {
// ...
});

error cannot be a string. If a string is provided as the second argument, then error is assumed to be omitted and the string will be used for message instead. This can lead to easy-to-miss mistakes. Please read the example in

throws

carefully if using a string as the second argument gets considered.

@since ― v10.0.0

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

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

execute
(() => new
var Promise: PromiseConstructor
new <unknown>(executor: (resolve: (value: unknown) => void, reject: (reason?: any) => void) => void) => Promise<unknown>

Creates a new Promise.

@param ― executor A callback used to initialize the promise. This callback is passed two arguments: a resolve callback used to resolve the promise with a value or the result of another promise, and a reject callback used to reject the promise with a provided reason or error.

Promise
(() => {})),
class TimeoutRejectedError
TimeoutRejectedError
);
await
const time: FakeTimeProvider
time
.
FakeTimeProvider.runAll(limit?: number): Promise<void>

Fires timers until none are left (including ones scheduled while firing). Throws after limit timers to catch infinite loops.

runAll
(); // 4 timeouts + 3 delays = 34 virtual seconds, instantly
await
const done: Promise<void>
done
;

time.advance('30s') moves the clock step by step instead. More patterns in Testing.