π― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes sleep and withTimeout β mainly functions, hooks, or classes. What the file header says: Abort-responsive sleep. Resolves after `ms` milliseconds, or immediately when `signal` aborts (so backoff loops don't block shutdown). By default, abort resolves silently; the caller should check `signal.aborted` after the await. Pass `throwOnAbort: true` to have abort reject β u.
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
Abort-responsive sleep. Resolves after `ms` milliseconds, or immediately when `signal` aborts (so backoff loops don't block shutdown). By default, abort resolves silently; the caller should check `signal.aborted` after the await. Pass `throwOnAbort: true` to have abort reject β useful when the sleep is deep inside a retry loop and you want the rejection to bubble up and cancel the whole operation. Pass `abortError` to customize the rejection error (implies `throwOnAbort: true`). Useful for retry loops that catch a specific error class (e.g. `APIUserAbortError`).
π€ Exports (heuristic)
sleepwithTimeout
π₯οΈ Source preview
/**
* Abort-responsive sleep. Resolves after `ms` milliseconds, or immediately
* when `signal` aborts (so backoff loops don't block shutdown).
*
* By default, abort resolves silently; the caller should check
* `signal.aborted` after the await. Pass `throwOnAbort: true` to have
* abort reject β useful when the sleep is deep inside a retry loop
* and you want the rejection to bubble up and cancel the whole operation.
*
* Pass `abortError` to customize the rejection error (implies
* `throwOnAbort: true`). Useful for retry loops that catch a specific
* error class (e.g. `APIUserAbortError`).
*/
export function sleep(
ms: number,
signal?: AbortSignal,
opts?: { throwOnAbort?: boolean; abortError?: () => Error; unref?: boolean },
): Promise<void> {
return new Promise((resolve, reject) => {
// Check aborted state BEFORE setting up the timer. If we defined
// onAbort first and called it synchronously here, it would reference
// `timer` while still in the Temporal Dead Zone.
if (signal?.aborted) {
if (opts?.throwOnAbort || opts?.abortError) {
void reject(opts.abortError?.() ?? new Error('aborted'))
} else {
void resolve()
}
return
}
const timer = setTimeout(
(signal, onAbort, resolve) => {
signal?.removeEventListener('abort', onAbort)
void resolve()
},
ms,
signal,
onAbort,
resolve,
)
function onAbort(): void {
clearTimeout(timer)
if (opts?.throwOnAbort || opts?.abortError) {
void reject(opts.abortError?.() ?? new Error('aborted'))
} else {
void resolve()
}
}
signal?.addEventListener('abort', onAbort, { once: true })
if (opts?.unref) {
timer.unref()
}
})
}
function rejectWithTimeout(reject: (e: Error) => void, message: string): void {
reject(new Error(message))
}
/**
* Race a promise against a timeout. Rejects with `Error(message)` if the
* promise doesn't settle within `ms`. The timeout timer is cleared when
* the promise settles (no dangling timer) and unref'd so it doesn't
* block process exit.
*
* Note: this doesn't cancel the underlying work β if the promise is
* backed by a runaway async operation, that keeps running. This just
* returns control to the caller.
*/
export function withTimeout<T>(
promise: Promise<T>,
ms: number,
message: string,
): Promise<T> {
let timer: ReturnType<typeof setTimeout> | undefined
const timeoutPromise = new Promise<never>((_, reject) => {
// eslint-disable-next-line no-restricted-syntax -- not a sleep: REJECTS after ms (timeout guard)
timer = setTimeout(rejectWithTimeout, ms, reject, message)
if (typeof timer === 'object') timer.unref?.()
})
return Promise.race([promise, timeoutPromise]).finally(() => {
if (timer !== undefined) clearTimeout(timer)
})
}