Skip to content
Buy me a coffee

fetch

fetch takes controller.signal directly and rejects with signal.reason when it aborts.

async function
function getJson<T>(url: string): Promise<T | undefined>
getJson
<
function (type parameter) T in getJson<T>(url: string): Promise<T | undefined>
T
>(
url: string
url
: string):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
function (type parameter) T in getJson<T>(url: string): Promise<T | undefined>
T
| undefined> {
using
using controller: EnhancedAbortController
controller
= new
new EnhancedAbortController(): EnhancedAbortController

An AbortController with .NET CancellationTokenSource features. It is an AbortController, and controller.signal is a real AbortSignal: pass it to fetch, AbortSignal.any, streams, timers and any other API that takes a signal.

@example

const controller = new EnhancedAbortController().abortAfter(TimeSpan.fromSeconds(5));
const response = await fetch(url, { signal: controller.signal });

EnhancedAbortController
().
EnhancedAbortController.abortAfter(delay: Delay): EnhancedAbortController

Aborts after delay, like .NET's CancelAfter: calling it again replaces the previous schedule, and Infinity / -1 / TimeSpan.infinite cancel it. The reason is a DOMException named TimeoutError, as with AbortSignal.timeout. The timer never keeps the process alive, and delays longer than 24.8 days work. Does nothing once aborted. Returns the controller, for chaining.

@throws ― RangeError for NaN, negative (other than -1) or non-numeric delays.

@throws ― Error if the controller was disposed.

abortAfter
(
class TimeSpan

An immutable time interval, stored in milliseconds. Modeled on .NET's TimeSpan.

@example

controller.abortAfter(TimeSpan.fromSeconds(30));
TimeSpan.fromMinutes(1).add(TimeSpan.fromSeconds(30)).totalSeconds; // 90

TimeSpan
.
TimeSpan.fromSeconds(seconds: number): TimeSpan
fromSeconds
(10));
try {
const
const response: Response
response
= await
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
(
url: string
url
, {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
:
using controller: EnhancedAbortController
controller
.
EnhancedAbortController.signal: EnhancedSignal

The controller's signal: a real AbortSignal, always the same object.

signal
});
if (!
const response: Response
response
.
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 response: Response
response
.
Response.status: number

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

MDN Reference

status
}`);
return (await
const response: Response
response
.
Body.json(): Promise<any>
json
()) as
function (type parameter) T in getJson<T>(url: string): Promise<T | undefined>
T
; // the body read is covered by the same deadline
} catch (
function (local var) error: unknown
error
) {
if (
function isTimeoutError(error: unknown): boolean

True if error is a timeout: a TimeoutError from abortAfter, timeout() or AbortSignal.timeout, also when wrapped: Node core APIs (fs/promises, timers/promises, streams, events.once) reject with an AbortError whose cause is the signal's reason.

isTimeoutError
(
function (local var) error: unknown
error
)) return
var undefined
undefined
;
throw
function (local var) error: unknown
error
;
}
}

One controller does both: no AbortSignal.any needed.

const
const controller: EnhancedAbortController
controller
= new
new EnhancedAbortController(): EnhancedAbortController

An AbortController with .NET CancellationTokenSource features. It is an AbortController, and controller.signal is a real AbortSignal: pass it to fetch, AbortSignal.any, streams, timers and any other API that takes a signal.

@example

const controller = new EnhancedAbortController().abortAfter(TimeSpan.fromSeconds(5));
const response = await fetch(url, { signal: controller.signal });

EnhancedAbortController
().
EnhancedAbortController.abortAfter(delay: Delay): EnhancedAbortController

Aborts after delay, like .NET's CancelAfter: calling it again replaces the previous schedule, and Infinity / -1 / TimeSpan.infinite cancel it. The reason is a DOMException named TimeoutError, as with AbortSignal.timeout. The timer never keeps the process alive, and delays longer than 24.8 days work. Does nothing once aborted. Returns the controller, for chaining.

@throws ― RangeError for NaN, negative (other than -1) or non-numeric delays.

@throws ― Error if the controller was disposed.

abortAfter
(15_000);
const cancelButton: HTMLButtonElement
cancelButton
.
GlobalEventHandlers.onclick: ((this: GlobalEventHandlers, ev: PointerEvent) => any) | null
onclick
= () =>
const controller: EnhancedAbortController
controller
.
AbortController.abort(reason?: any): void

The abort() method of the AbortController interface aborts an asynchronous operation before it has completed.

MDN Reference

abort
();
try {
function render(data: unknown): void
render
(await (await
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
('/api/export', {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
:
const controller: EnhancedAbortController
controller
.
EnhancedAbortController.signal: EnhancedSignal

The controller's signal: a real AbortSignal, always the same object.

signal
})).
Body.json(): Promise<any>
json
());
} catch (
var error: unknown
error
) {
if (!
function isAbortError(error: unknown): boolean

True for every way an aborted operation fails: this library's

AbortError

, and any error named AbortError or TimeoutError (the DOMExceptions that fetch, AbortSignal.timeout, abortAfter and throwIfAborted produce).

@example

try {
await fetch(url, { signal: controller.signal });
} catch (error) {
if (isAbortError(error)) return; // cancelled or timed out
throw error;
}

isAbortError
(
var error: unknown
error
)) throw
var error: unknown
error
;
} finally {
const controller: EnhancedAbortController
controller
.
EnhancedAbortController.dispose(): void

Releases what the controller holds: the scheduled abort and the listeners it put on linked parent signals. It does not abort (like .NET's Dispose); call abort() first if work should stop. abort() still works afterwards. Safe to call more than once; supports using.

dispose
();
}

Abort the previous request when a new one starts:

let
let current: EnhancedAbortController | undefined
current
:
class EnhancedAbortController

An AbortController with .NET CancellationTokenSource features. It is an AbortController, and controller.signal is a real AbortSignal: pass it to fetch, AbortSignal.any, streams, timers and any other API that takes a signal.

@example

const controller = new EnhancedAbortController().abortAfter(TimeSpan.fromSeconds(5));
const response = await fetch(url, { signal: controller.signal });

EnhancedAbortController
| undefined;
async function
function search(query: string): Promise<void>
search
(
query: string
query
: string) {
let current: EnhancedAbortController | undefined
current
?.
AbortController.abort(reason?: any): void

The abort() method of the AbortController interface aborts an asynchronous operation before it has completed.

MDN Reference

abort
();
const
const controller: EnhancedAbortController
controller
= (
let current: EnhancedAbortController | undefined
current
= new
new EnhancedAbortController(): EnhancedAbortController

An AbortController with .NET CancellationTokenSource features. It is an AbortController, and controller.signal is a real AbortSignal: pass it to fetch, AbortSignal.any, streams, timers and any other API that takes a signal.

@example

const controller = new EnhancedAbortController().abortAfter(TimeSpan.fromSeconds(5));
const response = await fetch(url, { signal: controller.signal });

EnhancedAbortController
().
EnhancedAbortController.abortAfter(delay: Delay): EnhancedAbortController

Aborts after delay, like .NET's CancelAfter: calling it again replaces the previous schedule, and Infinity / -1 / TimeSpan.infinite cancel it. The reason is a DOMException named TimeoutError, as with AbortSignal.timeout. The timer never keeps the process alive, and delays longer than 24.8 days work. Does nothing once aborted. Returns the controller, for chaining.

@throws ― RangeError for NaN, negative (other than -1) or non-numeric delays.

@throws ― Error if the controller was disposed.

abortAfter
(5000));
try {
const
const response: Response
response
= await
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
(`/search?q=${
function encodeURIComponent(uriComponent: string | number | boolean): string

Encodes a text string as a valid component of a Uniform Resource Identifier (URI).

@param ― uriComponent A value representing an unencoded URI component.

encodeURIComponent
(
query: string
query
)}`, {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
:
const controller: EnhancedAbortController
controller
.
EnhancedAbortController.signal: EnhancedSignal

The controller's signal: a real AbortSignal, always the same object.

signal
});
function show(results: unknown): void
show
(await
const response: Response
response
.
Body.json(): Promise<any>
json
());
} catch (
function (local var) error: unknown
error
) {
if (!
function isAbortError(error: unknown): boolean

True for every way an aborted operation fails: this library's

AbortError

, and any error named AbortError or TimeoutError (the DOMExceptions that fetch, AbortSignal.timeout, abortAfter and throwIfAborted produce).

@example

try {
await fetch(url, { signal: controller.signal });
} catch (error) {
if (isAbortError(error)) return; // cancelled or timed out
throw error;
}

isAbortError
(
function (local var) error: unknown
error
)) throw
function (local var) error: unknown
error
;
} finally {
const controller: EnhancedAbortController
controller
.
EnhancedAbortController.dispose(): void

Releases what the controller holds: the scheduled abort and the listeners it put on linked parent signals. It does not abort (like .NET's Dispose); call abort() first if work should stop. abort() still works afterwards. Safe to call more than once; supports using.

dispose
();
}
}

Allow a long download, but abort if no bytes arrive for 10 seconds:

async function
function download(url: string): Promise<number>
download
(
url: string
url
: string):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<number> {
using
using idle: EnhancedAbortController
idle
= new
new EnhancedAbortController(): EnhancedAbortController

An AbortController with .NET CancellationTokenSource features. It is an AbortController, and controller.signal is a real AbortSignal: pass it to fetch, AbortSignal.any, streams, timers and any other API that takes a signal.

@example

const controller = new EnhancedAbortController().abortAfter(TimeSpan.fromSeconds(5));
const response = await fetch(url, { signal: controller.signal });

EnhancedAbortController
().
EnhancedAbortController.abortAfter(delay: Delay): EnhancedAbortController

Aborts after delay, like .NET's CancelAfter: calling it again replaces the previous schedule, and Infinity / -1 / TimeSpan.infinite cancel it. The reason is a DOMException named TimeoutError, as with AbortSignal.timeout. The timer never keeps the process alive, and delays longer than 24.8 days work. Does nothing once aborted. Returns the controller, for chaining.

@throws ― RangeError for NaN, negative (other than -1) or non-numeric delays.

@throws ― Error if the controller was disposed.

abortAfter
(
class TimeSpan

An immutable time interval, stored in milliseconds. Modeled on .NET's TimeSpan.

@example

controller.abortAfter(TimeSpan.fromSeconds(30));
TimeSpan.fromMinutes(1).add(TimeSpan.fromSeconds(30)).totalSeconds; // 90

TimeSpan
.
TimeSpan.fromSeconds(seconds: number): TimeSpan
fromSeconds
(10));
const
const response: Response
response
= await
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
(
url: string
url
, {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
:
using idle: EnhancedAbortController
idle
.
EnhancedAbortController.signal: EnhancedSignal

The controller's signal: a real AbortSignal, always the same object.

signal
});
let
let bytes: number
bytes
= 0;
for await (const
const chunk: Uint8Array<ArrayBuffer>
chunk
of
const response: Response
response
.
Body.body: ReadableStream<Uint8Array<ArrayBuffer>> | null
body
!) {
let bytes: number
bytes
+=
const chunk: Uint8Array<ArrayBuffer>
chunk
.
Uint8Array<ArrayBuffer>.byteLength: number

The length in bytes of the array.

byteLength
;
using idle: EnhancedAbortController
idle
.
EnhancedAbortController.abortAfter(delay: Delay): EnhancedAbortController

Aborts after delay, like .NET's CancelAfter: calling it again replaces the previous schedule, and Infinity / -1 / TimeSpan.infinite cancel it. The reason is a DOMException named TimeoutError, as with AbortSignal.timeout. The timer never keeps the process alive, and delays longer than 24.8 days work. Does nothing once aborted. Returns the controller, for chaining.

@throws ― RangeError for NaN, negative (other than -1) or non-numeric delays.

@throws ― Error if the controller was disposed.

abortAfter
(
class TimeSpan

An immutable time interval, stored in milliseconds. Modeled on .NET's TimeSpan.

@example

controller.abortAfter(TimeSpan.fromSeconds(30));
TimeSpan.fromMinutes(1).add(TimeSpan.fromSeconds(30)).totalSeconds; // 90

TimeSpan
.
TimeSpan.fromSeconds(seconds: number): TimeSpan
fromSeconds
(10)); // progress: reset the deadline
}
return
let bytes: number
bytes
;
}

Retries with a per-attempt deadline and an overall budget

Section titled “Retries with a per-attempt deadline and an overall budget”
async function
function fetchWithRetry(url: string, signal?: AbortSignal): Promise<Response>
fetchWithRetry
(
url: string
url
: string,
signal: AbortSignal | undefined
signal
?:
interface AbortSignal

The AbortSignal interface represents a signal object that allows you to communicate with an asynchronous operation (such as a fetch request) and abort it if required via an AbortController object.

MDN Reference

AbortSignal
):
interface Promise<T>

Represents the completion of an asynchronous operation

Promise
<
interface Response

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

MDN Reference

Response
> {
using
using overall: EnhancedAbortController
overall
=
class EnhancedAbortController

An AbortController with .NET CancellationTokenSource features. It is an AbortController, and controller.signal is a real AbortSignal: pass it to fetch, AbortSignal.any, streams, timers and any other API that takes a signal.

@example

const controller = new EnhancedAbortController().abortAfter(TimeSpan.fromSeconds(5));
const response = await fetch(url, { signal: controller.signal });

EnhancedAbortController
.
EnhancedAbortController.createLinkedController<EnhancedAbortController>(this: new () => EnhancedAbortController, ...signals: Array<AbortSignal | null | undefined>): EnhancedAbortController

A controller that aborts as soon as any of signals aborts, with that signal's reason, like .NET's CreateLinkedTokenSource. Aborting the linked controller does not affect the parents. Its listeners on the parents are removed when it aborts or is disposed, so short-lived links on a long-lived parent don't leak. undefined/null entries (optional signals) and EnhancedAbortSignal.none are skipped.

createLinkedController
(
signal: AbortSignal | undefined
signal
).
EnhancedAbortController.abortAfter(delay: Delay): EnhancedAbortController

Aborts after delay, like .NET's CancelAfter: calling it again replaces the previous schedule, and Infinity / -1 / TimeSpan.infinite cancel it. The reason is a DOMException named TimeoutError, as with AbortSignal.timeout. The timer never keeps the process alive, and delays longer than 24.8 days work. Does nothing once aborted. Returns the controller, for chaining.

@throws ― RangeError for NaN, negative (other than -1) or non-numeric delays.

@throws ― Error if the controller was disposed.

abortAfter
(
class TimeSpan

An immutable time interval, stored in milliseconds. Modeled on .NET's TimeSpan.

@example

controller.abortAfter(TimeSpan.fromSeconds(30));
TimeSpan.fromMinutes(1).add(TimeSpan.fromSeconds(30)).totalSeconds; // 90

TimeSpan
.
TimeSpan.fromSeconds(seconds: number): TimeSpan
fromSeconds
(30));
for (let
let attempt: number
attempt
= 1; ;
let attempt: number
attempt
++) {
using
using perTry: EnhancedAbortController
perTry
=
class EnhancedAbortController

An AbortController with .NET CancellationTokenSource features. It is an AbortController, and controller.signal is a real AbortSignal: pass it to fetch, AbortSignal.any, streams, timers and any other API that takes a signal.

@example

const controller = new EnhancedAbortController().abortAfter(TimeSpan.fromSeconds(5));
const response = await fetch(url, { signal: controller.signal });

EnhancedAbortController
.
EnhancedAbortController.createLinkedController<EnhancedAbortController>(this: new () => EnhancedAbortController, ...signals: Array<AbortSignal | null | undefined>): EnhancedAbortController

A controller that aborts as soon as any of signals aborts, with that signal's reason, like .NET's CreateLinkedTokenSource. Aborting the linked controller does not affect the parents. Its listeners on the parents are removed when it aborts or is disposed, so short-lived links on a long-lived parent don't leak. undefined/null entries (optional signals) and EnhancedAbortSignal.none are skipped.

createLinkedController
(
using overall: EnhancedAbortController
overall
.
EnhancedAbortController.signal: EnhancedSignal

The controller's signal: a real AbortSignal, always the same object.

signal
).
EnhancedAbortController.abortAfter(delay: Delay): EnhancedAbortController

Aborts after delay, like .NET's CancelAfter: calling it again replaces the previous schedule, and Infinity / -1 / TimeSpan.infinite cancel it. The reason is a DOMException named TimeoutError, as with AbortSignal.timeout. The timer never keeps the process alive, and delays longer than 24.8 days work. Does nothing once aborted. Returns the controller, for chaining.

@throws ― RangeError for NaN, negative (other than -1) or non-numeric delays.

@throws ― Error if the controller was disposed.

abortAfter
(
class TimeSpan

An immutable time interval, stored in milliseconds. Modeled on .NET's TimeSpan.

@example

controller.abortAfter(TimeSpan.fromSeconds(30));
TimeSpan.fromMinutes(1).add(TimeSpan.fromSeconds(30)).totalSeconds; // 90

TimeSpan
.
TimeSpan.fromSeconds(seconds: number): TimeSpan
fromSeconds
(5));
try {
return await
function fetch(input: string | URL | Request, init?: RequestInit): Promise<Response> (+1 overload)
fetch
(
url: string
url
, {
RequestInit.signal?: AbortSignal | null | undefined

An AbortSignal to set request's signal.

signal
:
using perTry: EnhancedAbortController
perTry
.
EnhancedAbortController.signal: EnhancedSignal

The controller's signal: a real AbortSignal, always the same object.

signal
});
} catch (
function (local var) error: unknown
error
) {
// Give up on real errors, on the caller's cancel and on the overall budget; retry only a per-attempt timeout.
if (!
function isTimeoutError(error: unknown): boolean

True if error is a timeout: a TimeoutError from abortAfter, timeout() or AbortSignal.timeout, also when wrapped: Node core APIs (fs/promises, timers/promises, streams, events.once) reject with an AbortError whose cause is the signal's reason.

isTimeoutError
(
function (local var) error: unknown
error
) ||
using overall: EnhancedAbortController
overall
.
EnhancedAbortController.signal: EnhancedSignal

The controller's signal: a real AbortSignal, always the same object.

signal
.
AbortSignal.aborted: boolean

The aborted read-only property returns a value that indicates whether the asynchronous operations the signal is communicating with are aborted (true) or not (false).

MDN Reference

aborted
||
let attempt: number
attempt
=== 3) throw
function (local var) error: unknown
error
;
}
}
}

Here the per-attempt child times out with its own TimeoutError while overall stays live; when overall (or the caller’s signal) aborts, its reason flows into the child and the loop stops.

fetch rejects with the signal’s reason: TimeoutError for deadlines, AbortError for abort(), or your custom reason. Classify with isAbortError / isTimeoutError; see Reasons & errors.