@reduxjs/toolkit
Version:
The official, opinionated, batteries-included toolset for efficient Redux development
96 lines (89 loc) • 2.7 kB
text/typescript
import { TaskAbortError } from './exceptions'
import type { AbortSignalWithReason, TaskResult } from './types'
import { addAbortSignalListener, catchRejection } from './utils'
/**
* Synchronously raises {@link TaskAbortError} if the task tied to the input `signal` has been cancelled.
* @param signal
* @param reason
* @see {TaskAbortError}
*/
export const validateActive = (signal: AbortSignal): void => {
if (signal.aborted) {
throw new TaskAbortError((signal as AbortSignalWithReason<string>).reason)
}
}
/**
* Returns a promise that will reject {@link TaskAbortError} if the task is cancelled.
* @param signal
* @returns
*/
export const promisifyAbortSignal = (
signal: AbortSignalWithReason<string>
): Promise<never> => {
return catchRejection(
new Promise<never>((_, reject) => {
const notifyRejection = () => reject(new TaskAbortError(signal.reason))
if (signal.aborted) {
notifyRejection()
} else {
addAbortSignalListener(signal, notifyRejection)
}
})
)
}
/**
* Runs a task and returns promise that resolves to {@link TaskResult}.
* Second argument is an optional `cleanUp` function that always runs after task.
*
* **Note:** `runTask` runs the executor in the next microtask.
* @returns
*/
export const runTask = async <T>(
task: () => Promise<T>,
cleanUp?: () => void
): Promise<TaskResult<T>> => {
try {
await Promise.resolve()
const value = await task()
return {
status: 'ok',
value,
}
} catch (error: any) {
return {
status: error instanceof TaskAbortError ? 'cancelled' : 'rejected',
error,
}
} finally {
cleanUp?.()
}
}
/**
* Given an input `AbortSignal` and a promise returns another promise that resolves
* as soon the input promise is provided or rejects as soon as
* `AbortSignal.abort` is `true`.
* @param signal
* @returns
*/
export const createPause = <T>(signal: AbortSignal) => {
return (promise: Promise<T>): Promise<T> => {
return catchRejection(
Promise.race([promisifyAbortSignal(signal), promise]).then((output) => {
validateActive(signal)
return output
})
)
}
}
/**
* Given an input `AbortSignal` and `timeoutMs` returns a promise that resolves
* after `timeoutMs` or rejects as soon as `AbortSignal.abort` is `true`.
* @param signal
* @returns
*/
export const createDelay = (signal: AbortSignal) => {
const pause = createPause<void>(signal)
return (timeoutMs: number): Promise<void> => {
return pause(new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)))
}
}