@jcoreio/abortable
Version:
memory-leak-proof function to wrap a promise to reject when a signal is aborted
40 lines (36 loc) • 1.35 kB
text/typescript
export const newAbortError = () =>
new DOMException('This operation was aborted', 'AbortError')
const noop = () => {}
export function abortable<T>(
promise: Promise<T>,
signal: AbortSignal | undefined
): Promise<T> {
if (!signal) return promise
return new Promise<T>((resolve, reject) => {
const cleanup = () => {
const callbacks = { resolve, reject }
// Prevent memory leaks. If the input promise never resolves, then the handlers
// below would retain this enclosing Promise's resolve and reject callbacks,
// which would retain the enclosing Promise and anything waiting on it.
// By replacing references to these callbacks, we enable the enclosing Promise to
// be garbage collected
resolve = noop
reject = noop
// Memory could also leak if the signal never aborts, unless we remove the abort
// handler
signal.removeEventListener('abort', onAbort)
return callbacks
}
const onAbort = () => cleanup().reject(signal.reason)
promise.then(
(value) => cleanup().resolve(value),
(error: unknown) => cleanup().reject(error)
)
if (signal.aborted) {
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
reject(signal.reason)
} else {
signal.addEventListener('abort', onAbort)
}
})
}