-
Notifications
You must be signed in to change notification settings - Fork 3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ability to pass an AbortSignal to Bun.sleep #17866
Comments
Something like the code bellow right? function sleep(
ms: number,
abortSignal: AbortSignal | null = null,
throwOnAbort: boolean = true
) {
const { promise, resolve, reject } = Promise.withResolvers();
const timeout = setTimeout(resolve, ms);
if (abortSignal) {
abortSignal.addEventListener("abort", () => {
clearTimeout(timeout);
if (throwOnAbort) {
reject(abortSignal.reason);
} else {
resolve(abortSignal.reason);
}
});
}
return promise;
} |
@cirospaciari yes, pretty much... If Here's my take on the implementation with function overloads so that function sleep(ms: number): Promise<void>;
function sleep(ms: number, options: { signal: AbortSignal; throwOnAbort?: boolean }): Promise<void>;
function sleep(ms: number, { signal, throwOnAbort = true }: { signal?: AbortSignal; throwOnAbort?: boolean } = {}) {
const { promise, resolve, reject } = Promise.withResolvers();
// handling signals that are already aborted
if (signal?.aborted) {
if (throwOnAbort) {
reject(signal.reason);
} else {
resolve(signal.reason);
}
return promise;
}
const timeout = setTimeout(resolve, ms);
signal?.addEventListener('abort', () => {
clearTimeout(timeout);
if (throwOnAbort) {
reject(signal.reason);
} else {
resolve(signal.reason);
}
});
return promise;
} Usage: const abortController = new AbortController();
const signal = abortController.signal;
await sleep(1000);
await sleep(1000, { signal });
await sleep(1000, { signal, throwOnAbort: true });
await sleep(1000, { signal, throwOnAbort: false });
// @ts-expect-error (throws TSC error: "Property 'signal' is missing")
await sleep(1000, { throwOnAbort: true }); Tests: expect(() => sleep(1000, { signal: AbortSignal.timeout(1) })).toThrow();
expect(() => sleep(1000, { signal: AbortSignal.timeout(1), throwOnAbort: false })).not.toThrow();
expect(() => sleep(1000, { signal: AbortSignal.abort('already aborted') })).toThrow(); |
We more or less have this already in the form of |
What is the problem this feature would solve?
The
Bun.sleep()
method is really handy. I think it would be even better if it supported AbortSignals.There's a lot of cases when it's crucial to stop the timer immediately and this isn't possible with
Bun.sleep
. I have a custom cancelable sleep utility in my codebase but it'd be nice if I didn't have to – the globalBun.sleep()
is a lot nicer 😄What is the feature you are proposing to solve the problem?
Proposed API change:
Allow passing an
AbortSignal
to the Bun'ssleep
/sleepAsync
methods to make them "cancelable".This will not be a breaking change.
Now, whether it should throw or not when the signal is aborted, I think ideally it should, but an argument can still be made that a quiet return could be preferable in scenarios where aborts are an expected part of the normal flow, reducing the need for excessive error-handling boilerplate and treating cancellation as a valid outcome rather than an exception.
API proposal:
options: { signal: AbortSignal; throwOnAbort?: boolean }
Full implementation draft specified below
Alternative API proposal:
options?: { signal?: AbortSignal }
.sleep
will always throws on signal's abort.Always throwing on abort is also consistent with other built-in APIs. The
{ throwOnAbort: false }
behavior from above could be achieved with a simple.catch()
:What alternatives have you considered?
Custom sleep utility
The text was updated successfully, but these errors were encountered: