UNPKG

2.7 kBPlain TextView Raw
1import { TaskAbortError } from './exceptions'
2import type { AbortSignalWithReason, TaskResult } from './types'
3import { addAbortSignalListener, catchRejection } from './utils'
4
5/**
6 * Synchronously raises {@link TaskAbortError} if the task tied to the input `signal` has been cancelled.
7 * @param signal
8 * @param reason
9 * @see {TaskAbortError}
10 */
11export const validateActive = (signal: AbortSignal): void => {
12 if (signal.aborted) {
13 throw new TaskAbortError((signal as AbortSignalWithReason<string>).reason)
14 }
15}
16
17/**
18 * Returns a promise that will reject {@link TaskAbortError} if the task is cancelled.
19 * @param signal
20 * @returns
21 */
22export const promisifyAbortSignal = (
23 signal: AbortSignalWithReason<string>
24): Promise<never> => {
25 return catchRejection(
26 new Promise<never>((_, reject) => {
27 const notifyRejection = () => reject(new TaskAbortError(signal.reason))
28
29 if (signal.aborted) {
30 notifyRejection()
31 } else {
32 addAbortSignalListener(signal, notifyRejection)
33 }
34 })
35 )
36}
37
38/**
39 * Runs a task and returns promise that resolves to {@link TaskResult}.
40 * Second argument is an optional `cleanUp` function that always runs after task.
41 *
42 * **Note:** `runTask` runs the executor in the next microtask.
43 * @returns
44 */
45export const runTask = async <T>(
46 task: () => Promise<T>,
47 cleanUp?: () => void
48): Promise<TaskResult<T>> => {
49 try {
50 await Promise.resolve()
51 const value = await task()
52 return {
53 status: 'ok',
54 value,
55 }
56 } catch (error: any) {
57 return {
58 status: error instanceof TaskAbortError ? 'cancelled' : 'rejected',
59 error,
60 }
61 } finally {
62 cleanUp?.()
63 }
64}
65
66/**
67 * Given an input `AbortSignal` and a promise returns another promise that resolves
68 * as soon the input promise is provided or rejects as soon as
69 * `AbortSignal.abort` is `true`.
70 * @param signal
71 * @returns
72 */
73export const createPause = <T>(signal: AbortSignal) => {
74 return (promise: Promise<T>): Promise<T> => {
75 return catchRejection(
76 Promise.race([promisifyAbortSignal(signal), promise]).then((output) => {
77 validateActive(signal)
78 return output
79 })
80 )
81 }
82}
83
84/**
85 * Given an input `AbortSignal` and `timeoutMs` returns a promise that resolves
86 * after `timeoutMs` or rejects as soon as `AbortSignal.abort` is `true`.
87 * @param signal
88 * @returns
89 */
90export const createDelay = (signal: AbortSignal) => {
91 const pause = createPause<void>(signal)
92 return (timeoutMs: number): Promise<void> => {
93 return pause(new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)))
94 }
95}
96
\No newline at end of file