Skip to content
Buy me a coffee

Timeout

Timeout stops waiting for a call that takes too long. It aborts context.signal, so fetch and other signal-aware I/O stop too, and fails with TimeoutRejectedError.

Use it on every call to a remote dependency: without one, a hung connection holds a request, a socket and memory forever. A timeout only frees the caller. If your I/O ignores the signal (most SQL drivers), the work keeps running on the other side; see Databases.

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 '@ylcnfrht/resilience';
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
().
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>.build(): ResiliencePipeline<any, TimeoutRejectedError>
build
();
// Pass the signal on, or the timeout can't cancel the request.
const
const res: Response
res
= 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
}) =>
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
('https://api.example.com/orders', {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
}));

addTimeout accepts a number of milliseconds, a duration string, or options: addTimeout({ timeout: '2s', onTimeout }).

  1. The strategy computes the timeout (timeoutGenerator, else timeout) and starts a timer.
  2. The callback receives a new signal linked to the caller’s: it aborts when the caller aborts or when the timer fires.
  3. When the timer fires, the signal is aborted with the TimeoutRejectedError as its reason, onTimeout runs, and the execution fails with that error.

What happens to a callback that is still running depends on the mode:

Mode When the time is up Polly v8 equivalent
Aggressive (default) Returns TimeoutRejectedError at once, even if the callback ignores the signal. The callback may keep running in the background; its result is discarded. none (Polly’s pessimistic mode is close)
cooperative: true Aborts the signal, then waits for the callback to settle, so no work outlives the timeout. A callback that ignores the signal delays the error until it finishes. TimeoutStrategy
cooperative: true, keepLateResult: true As cooperative, but if the callback still succeeds after the deadline, that result is returned instead of the error. none

If the caller aborts (not the timer), the timeout strategy returns the callback’s outcome unchanged; no timeout error is raised and onTimeout is not called.

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 '@ylcnfrht/resilience';
// Wait for the report job to stop before failing, and keep its result if it finishes anyway.
const
const pipeline: ResiliencePipeline<string, TimeoutRejectedError>
pipeline
= new
new ResiliencePipelineBuilder<string, never, never>(options?: ResiliencePipelineBuilderOptions): ResiliencePipelineBuilder<string, 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
<string>()
.
ResiliencePipelineBuilder<string, never, never>.addTimeout(options: Duration | TimeoutOptions): Next<string, never, never, TimeoutRejectedError>

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

addTimeout
({
TimeoutOptions.timeout?: Duration | undefined

Default 30 s. Infinity disables the timeout.

timeout
: '30s',
TimeoutOptions.cooperative?: boolean | undefined

false (default): give up as soon as the time is up, even if the callback ignores the signal. true: abort the signal, then wait for the callback to finish (Polly v8 behaviour), so no work outlives the timeout.

cooperative
: true,
TimeoutOptions.keepLateResult?: boolean | undefined

With cooperative: if the callback still succeeds after the deadline, return its result instead of the timeout error.

keepLateResult
: true })
.
ResiliencePipelineBuilder<string, TimeoutRejectedError, never>.build(): ResiliencePipeline<string, TimeoutRejectedError>
build
();
const
const report: string
report
= await
const pipeline: ResiliencePipeline<string, TimeoutRejectedError>
pipeline
.
ResiliencePipeline<string, TimeoutRejectedError>.execute<string>(callback: (context: ResilienceContext) => MaybePromise<string>, options?: ExecuteOptions): Promise<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 runReport(signal: AbortSignal): Promise<string>
runReport
(
signal: AbortSignal

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

signal
));

Strategies run in the order they’re added; the first is the outermost. The position of the timeout decides what it limits:

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 '@ylcnfrht/resilience';
// Each attempt gets 2 s; with 3 retries the call can take 4 × 2 s plus delays.
const
const perAttempt: ResiliencePipeline<any, TimeoutRejectedError>
perAttempt
= 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 }).
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>.build(): ResiliencePipeline<any, TimeoutRejectedError>
build
();
// All attempts and delays together get 5 s; the retry wait is cut short too.
const
const total: ResiliencePipeline<any, TimeoutRejectedError>
total
= 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
('5s').
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.addRetry(options?: RetryOptions<any>): Next<any, never, never, TimeoutRejectedError>
addRetry
({
RetryOptions<any>.maxRetryAttempts?: number | undefined

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

maxRetryAttempts
: 3 }).
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.build(): ResiliencePipeline<any, TimeoutRejectedError>
build
();
// Both: 10 s overall, 2 s per attempt.
const
const both: ResiliencePipeline<any, TimeoutRejectedError>
both
= 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
('10s')
.
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.addRetry(options?: RetryOptions<any>): Next<any, never, never, TimeoutRejectedError>
addRetry
({
RetryOptions<any>.maxRetryAttempts?: number | undefined

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

maxRetryAttempts
: 3,
RetryOptions<any>.delay?: Duration | undefined

Base delay. Default 2000 ms.

delay
: '200ms' })
.
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.addTimeout(options: Duration | TimeoutOptions): Next<any, TimeoutRejectedError, never, TimeoutRejectedError>

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

addTimeout
('2s')
.
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.build(): ResiliencePipeline<any, TimeoutRejectedError>
build
();

A timed-out attempt is retried by the default retry predicate: a timeout is a transient failure, not a rejection.

A pipeline executed inside another one inherits the outer signal, so an outer timeout also cancels nested calls. See Context, signals & deadline propagation.

timeoutGenerator picks the timeout per execution. It receives { context } and may be async. Returning 0, a negative number or Infinity runs that execution without a timeout.

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 '@ylcnfrht/resilience';
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
()
.
ResiliencePipelineBuilder<any, never, never>.addTimeout(options: Duration | TimeoutOptions): Next<any, never, never, TimeoutRejectedError>

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

addTimeout
({
TimeoutOptions.timeoutGenerator?: ((args: {
readonly context: ResilienceContext;
}) => MaybePromise<Duration>) | undefined
timeoutGenerator
: ({
context: ResilienceContext
context
}) => (
context: ResilienceContext
context
.
ResilienceContext.operationKey: string | undefined
operationKey
=== 'export' ? '2m' : '3s'),
})
.
ResiliencePipelineBuilder<any, TimeoutRejectedError, never>.build(): ResiliencePipeline<any, TimeoutRejectedError>
build
();
await
const pipeline: ResiliencePipeline<any, TimeoutRejectedError>
pipeline
.
ResiliencePipeline<any, TimeoutRejectedError>.execute<string>(callback: (context: ResilienceContext) => MaybePromise<string>, options?: ExecuteOptions): Promise<string>

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

execute
(async () => 'done', {
ExecuteOptions.operationKey?: string | undefined
operationKey
: 'export' });
Option Type Default Description
name string 'Timeout' Strategy name in telemetry and getStrategy().
timeout Duration 30000 (30 s) Time allowed. Must be > 0. Infinity disables the timeout.
timeoutGenerator ({ context }) => Duration (or a promise) none Per-execution timeout; overrides timeout. ≤ 0 or Infinity means no timeout.
onTimeout (args: OnTimeoutArgs) => void none Called when the timeout fires. Gets context and timeout (ms). May be async; it’s awaited before the error is returned.
cooperative boolean false Wait for the callback to settle after aborting it.
keepLateResult boolean false With cooperative: return a success that arrives after the deadline.

Duration is milliseconds or a string: '250ms', '30s', '5m', '1h', '1d', '2w'.

Telemetry event Callback Args
OnTimeout onTimeout { context, timeout }
Error code HTTP Fields
TimeoutRejectedError TIMEOUT 504 timeout (ms), source ({ pipelineName, strategyName })

Check for it with isTimeoutError(error); it works across duplicate copies of the package, unlike instanceof. Adding a timeout adds TimeoutRejectedError to the pipeline’s typed error union, so Outcome.match needs a TIMEOUT branch.

The same error object is the abort reason of the callback’s signal, so code deep inside the call can tell a timeout from a caller cancel:

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

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
}) => {
signal: AbortSignal

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

signal
.
AbortSignal.addEventListener<"abort">(type: "abort", listener: (this: AbortSignal, ev: Event) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)

The addEventListener() method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.

MDN Reference

addEventListener
('abort', () => {
if (
function isTimeoutError(error: unknown): error is TimeoutRejectedError
isTimeoutError
(
signal: AbortSignal

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

signal
.
AbortSignal.reason: any

The reason read-only property returns a JavaScript value that indicates the abort reason.

MDN Reference

reason
))
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.warn(message?: any, ...optionalParams: any[]): void (+1 overload)

The console.warn() function is an alias for

error

.

@since ― v0.1.100

warn
('gave up after',
signal: AbortSignal

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

signal
.
AbortSignal.reason: TimeoutRejectedError

The reason read-only property returns a JavaScript value that indicates the abort reason.

MDN Reference

reason
.
TimeoutRejectedError.timeout: number
timeout
, 'ms');
});
});
  • Timeout outside retry when you meant per attempt. One slow attempt then eats the whole budget and nothing is retried.
  • cooperative with I/O that ignores the signal. The caller waits as long as without a timeout. Keep the default mode there.
  • Expecting a circuit breaker to ignore timeouts. It counts them as failures, which is usually what you want.