source dump of claude code
at main 84 lines 2.9 kB view raw
1/** 2 * Abort-responsive sleep. Resolves after `ms` milliseconds, or immediately 3 * when `signal` aborts (so backoff loops don't block shutdown). 4 * 5 * By default, abort resolves silently; the caller should check 6 * `signal.aborted` after the await. Pass `throwOnAbort: true` to have 7 * abort reject — useful when the sleep is deep inside a retry loop 8 * and you want the rejection to bubble up and cancel the whole operation. 9 * 10 * Pass `abortError` to customize the rejection error (implies 11 * `throwOnAbort: true`). Useful for retry loops that catch a specific 12 * error class (e.g. `APIUserAbortError`). 13 */ 14export function sleep( 15 ms: number, 16 signal?: AbortSignal, 17 opts?: { throwOnAbort?: boolean; abortError?: () => Error; unref?: boolean }, 18): Promise<void> { 19 return new Promise((resolve, reject) => { 20 // Check aborted state BEFORE setting up the timer. If we defined 21 // onAbort first and called it synchronously here, it would reference 22 // `timer` while still in the Temporal Dead Zone. 23 if (signal?.aborted) { 24 if (opts?.throwOnAbort || opts?.abortError) { 25 void reject(opts.abortError?.() ?? new Error('aborted')) 26 } else { 27 void resolve() 28 } 29 return 30 } 31 const timer = setTimeout( 32 (signal, onAbort, resolve) => { 33 signal?.removeEventListener('abort', onAbort) 34 void resolve() 35 }, 36 ms, 37 signal, 38 onAbort, 39 resolve, 40 ) 41 function onAbort(): void { 42 clearTimeout(timer) 43 if (opts?.throwOnAbort || opts?.abortError) { 44 void reject(opts.abortError?.() ?? new Error('aborted')) 45 } else { 46 void resolve() 47 } 48 } 49 signal?.addEventListener('abort', onAbort, { once: true }) 50 if (opts?.unref) { 51 timer.unref() 52 } 53 }) 54} 55 56function rejectWithTimeout(reject: (e: Error) => void, message: string): void { 57 reject(new Error(message)) 58} 59 60/** 61 * Race a promise against a timeout. Rejects with `Error(message)` if the 62 * promise doesn't settle within `ms`. The timeout timer is cleared when 63 * the promise settles (no dangling timer) and unref'd so it doesn't 64 * block process exit. 65 * 66 * Note: this doesn't cancel the underlying work — if the promise is 67 * backed by a runaway async operation, that keeps running. This just 68 * returns control to the caller. 69 */ 70export function withTimeout<T>( 71 promise: Promise<T>, 72 ms: number, 73 message: string, 74): Promise<T> { 75 let timer: ReturnType<typeof setTimeout> | undefined 76 const timeoutPromise = new Promise<never>((_, reject) => { 77 // eslint-disable-next-line no-restricted-syntax -- not a sleep: REJECTS after ms (timeout guard) 78 timer = setTimeout(rejectWithTimeout, ms, reject, message) 79 if (typeof timer === 'object') timer.unref?.() 80 }) 81 return Promise.race([promise, timeoutPromise]).finally(() => { 82 if (timer !== undefined) clearTimeout(timer) 83 }) 84}