2024-01-01 21:11:32 +00:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2023-03-18 12:36:00 +00:00
|
|
|
// This module is browser compatible.
|
|
|
|
|
2024-04-11 21:23:54 +00:00
|
|
|
import { createAbortError } from "./_util.ts";
|
|
|
|
|
2022-11-25 11:40:23 +00:00
|
|
|
/**
|
2024-05-22 00:40:43 +00:00
|
|
|
* Make a {@linkcode Promise} abortable with the given signal.
|
|
|
|
*
|
|
|
|
* @typeParam T The type of the provided and returned promise.
|
|
|
|
* @param p The promise to make abortable.
|
|
|
|
* @param signal The signal to abort the promise with.
|
|
|
|
* @returns A promise that can be aborted.
|
2022-11-25 11:40:23 +00:00
|
|
|
*
|
2024-05-22 05:08:36 +00:00
|
|
|
* @example Usage
|
2024-05-22 00:40:43 +00:00
|
|
|
* ```ts no-eval
|
2023-12-01 02:19:22 +00:00
|
|
|
* import {
|
|
|
|
* abortable,
|
|
|
|
* delay,
|
2024-04-29 02:57:30 +00:00
|
|
|
* } from "@std/async";
|
2022-11-25 11:40:23 +00:00
|
|
|
*
|
|
|
|
* const p = delay(1000);
|
|
|
|
* const c = new AbortController();
|
|
|
|
* setTimeout(() => c.abort(), 100);
|
|
|
|
*
|
|
|
|
* // Below throws `DOMException` after 100 ms
|
|
|
|
* await abortable(p, c.signal);
|
|
|
|
* ```
|
|
|
|
*/
|
2022-02-21 04:51:39 +00:00
|
|
|
export function abortable<T>(p: Promise<T>, signal: AbortSignal): Promise<T>;
|
2022-11-25 11:40:23 +00:00
|
|
|
/**
|
2024-05-22 00:40:43 +00:00
|
|
|
* Make an {@linkcode AsyncIterable} abortable with the given signal.
|
|
|
|
*
|
|
|
|
* @typeParam T The type of the provided and returned async iterable.
|
|
|
|
* @param p The async iterable to make abortable.
|
|
|
|
* @param signal The signal to abort the promise with.
|
|
|
|
* @returns An async iterable that can be aborted.
|
2022-11-25 11:40:23 +00:00
|
|
|
*
|
2024-05-22 05:08:36 +00:00
|
|
|
* @example Usage
|
2024-05-22 00:40:43 +00:00
|
|
|
* ```ts no-eval
|
2023-12-01 02:19:22 +00:00
|
|
|
* import {
|
|
|
|
* abortable,
|
|
|
|
* delay,
|
2024-04-29 02:57:30 +00:00
|
|
|
* } from "@std/async";
|
2022-11-25 11:40:23 +00:00
|
|
|
*
|
|
|
|
* const p = async function* () {
|
|
|
|
* yield "Hello";
|
|
|
|
* await delay(1000);
|
|
|
|
* yield "World";
|
|
|
|
* };
|
|
|
|
* const c = new AbortController();
|
|
|
|
* setTimeout(() => c.abort(), 100);
|
|
|
|
*
|
|
|
|
* // Below throws `DOMException` after 100 ms
|
|
|
|
* // and items become `["Hello"]`
|
|
|
|
* const items: string[] = [];
|
|
|
|
* for await (const item of abortable(p(), c.signal)) {
|
|
|
|
* items.push(item);
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*/
|
2022-02-21 04:51:39 +00:00
|
|
|
export function abortable<T>(
|
|
|
|
p: AsyncIterable<T>,
|
|
|
|
signal: AbortSignal,
|
|
|
|
): AsyncGenerator<T>;
|
|
|
|
export function abortable<T>(
|
|
|
|
p: Promise<T> | AsyncIterable<T>,
|
|
|
|
signal: AbortSignal,
|
|
|
|
): Promise<T> | AsyncIterable<T> {
|
|
|
|
if (p instanceof Promise) {
|
|
|
|
return abortablePromise(p, signal);
|
|
|
|
} else {
|
|
|
|
return abortableAsyncIterable(p, signal);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-11-25 11:40:23 +00:00
|
|
|
/**
|
2024-05-22 00:40:43 +00:00
|
|
|
* Make a {@linkcode Promise} abortable with the given signal.
|
|
|
|
*
|
|
|
|
* @typeParam T The type of the provided and returned promise.
|
|
|
|
* @param p The promise to make abortable.
|
|
|
|
* @param signal The signal to abort the promise with.
|
|
|
|
* @returns A promise that can be aborted.
|
2022-11-25 11:40:23 +00:00
|
|
|
*
|
2024-05-22 05:08:36 +00:00
|
|
|
* @example Usage
|
2024-05-22 00:40:43 +00:00
|
|
|
* ```ts no-eval
|
2024-04-29 02:57:30 +00:00
|
|
|
* import { abortablePromise } from "@std/async/abortable";
|
2022-11-25 11:40:23 +00:00
|
|
|
*
|
|
|
|
* const request = fetch("https://example.com");
|
|
|
|
*
|
|
|
|
* const c = new AbortController();
|
|
|
|
* setTimeout(() => c.abort(), 100);
|
|
|
|
*
|
|
|
|
* const p = abortablePromise(request, c.signal);
|
|
|
|
*
|
|
|
|
* // The below throws if the request didn't resolve in 100ms
|
|
|
|
* await p;
|
|
|
|
* ```
|
|
|
|
*/
|
2022-03-17 05:09:32 +00:00
|
|
|
export function abortablePromise<T>(
|
|
|
|
p: Promise<T>,
|
|
|
|
signal: AbortSignal,
|
|
|
|
): Promise<T> {
|
2022-02-21 04:51:39 +00:00
|
|
|
if (signal.aborted) {
|
|
|
|
return Promise.reject(createAbortError(signal.reason));
|
|
|
|
}
|
2023-11-10 03:31:16 +00:00
|
|
|
const { promise, reject } = Promise.withResolvers<never>();
|
|
|
|
const abort = () => reject(createAbortError(signal.reason));
|
2022-02-21 04:51:39 +00:00
|
|
|
signal.addEventListener("abort", abort, { once: true });
|
2023-12-07 21:36:52 +00:00
|
|
|
return Promise.race([promise, p]).finally(() => {
|
|
|
|
signal.removeEventListener("abort", abort);
|
|
|
|
});
|
2022-02-21 04:51:39 +00:00
|
|
|
}
|
|
|
|
|
2022-11-25 11:40:23 +00:00
|
|
|
/**
|
2024-05-22 00:40:43 +00:00
|
|
|
* Make an {@linkcode AsyncIterable} abortable with the given signal.
|
|
|
|
*
|
|
|
|
* @typeParam T The type of the provided and returned async iterable.
|
|
|
|
* @param p The async iterable to make abortable.
|
|
|
|
* @param signal The signal to abort the promise with.
|
|
|
|
* @returns An async iterable that can be aborted.
|
2022-11-25 11:40:23 +00:00
|
|
|
*
|
2024-05-22 05:08:36 +00:00
|
|
|
* @example Usage
|
2024-05-22 00:40:43 +00:00
|
|
|
* ```ts no-eval
|
2023-12-01 02:19:22 +00:00
|
|
|
* import {
|
|
|
|
* abortableAsyncIterable,
|
|
|
|
* delay,
|
2024-04-29 02:57:30 +00:00
|
|
|
* } from "@std/async";
|
2022-11-25 11:40:23 +00:00
|
|
|
*
|
|
|
|
* const p = async function* () {
|
|
|
|
* yield "Hello";
|
|
|
|
* await delay(1000);
|
|
|
|
* yield "World";
|
|
|
|
* };
|
|
|
|
* const c = new AbortController();
|
|
|
|
* setTimeout(() => c.abort(), 100);
|
|
|
|
*
|
|
|
|
* // Below throws `DOMException` after 100 ms
|
|
|
|
* // and items become `["Hello"]`
|
|
|
|
* const items: string[] = [];
|
|
|
|
* for await (const item of abortableAsyncIterable(p(), c.signal)) {
|
|
|
|
* items.push(item);
|
|
|
|
* }
|
|
|
|
* ```
|
|
|
|
*/
|
2022-03-17 05:09:32 +00:00
|
|
|
export async function* abortableAsyncIterable<T>(
|
2022-02-21 04:51:39 +00:00
|
|
|
p: AsyncIterable<T>,
|
|
|
|
signal: AbortSignal,
|
|
|
|
): AsyncGenerator<T> {
|
|
|
|
if (signal.aborted) {
|
|
|
|
throw createAbortError(signal.reason);
|
|
|
|
}
|
2023-11-10 03:31:16 +00:00
|
|
|
const { promise, reject } = Promise.withResolvers<never>();
|
|
|
|
const abort = () => reject(createAbortError(signal.reason));
|
2022-02-21 04:51:39 +00:00
|
|
|
signal.addEventListener("abort", abort, { once: true });
|
|
|
|
|
|
|
|
const it = p[Symbol.asyncIterator]();
|
|
|
|
while (true) {
|
2023-12-07 21:36:52 +00:00
|
|
|
const race = Promise.race([promise, it.next()]);
|
|
|
|
race.catch(() => {
|
|
|
|
signal.removeEventListener("abort", abort);
|
|
|
|
});
|
|
|
|
const { done, value } = await race;
|
2022-02-21 04:51:39 +00:00
|
|
|
if (done) {
|
|
|
|
signal.removeEventListener("abort", abort);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
yield value;
|
|
|
|
}
|
|
|
|
}
|