Skip to content

sleep function ​

Waits for a delay, cancellable with an AbortSignal that clears the timer.

ts
import { sleep } from 'utils';

Signature ​

ts
export async function sleep(ms = 0, signal?: AbortSignal): Promise<void>

Parameters ​

NameDescription
msThe delay in milliseconds. Defaults to 0.
signalThe signal that cancels the wait.

Returns ​

A promise resolved once the delay has elapsed.

Errors ​

  • Rejects Error: With the signal's reason when signal is aborted.

Example ​

ts
await sleep(100);
await sleep(1000, controller.signal); // rejects as soon as controller.abort() is called

Tests ​

Line coverage: 100 %.

  • ✅ resolves after the delay
  • ✅ rejects when the signal is aborted, and clears the timer
  • ✅ rejects at once when the signal is already aborted
  • ✅ wraps a non-error abort reason
  • ✅ removes its abort listener once resolved

Source ​

src/async/sleep.ts (on GitHub)

Source code
ts
function abortReason(signal: AbortSignal): Error {
  const reason: unknown = signal.reason;
  return reason instanceof Error ? reason : new Error(String(reason));
}

/**
 * Waits for a delay, cancellable with an `AbortSignal` that clears the timer.
 *
 * @param ms - The delay in milliseconds. Defaults to `0`.
 * @param signal - The signal that cancels the wait.
 * @returns A promise resolved once the delay has elapsed.
 * @rejects {Error} With the signal's reason when `signal` is aborted.
 * @example
 * await sleep(100);
 * await sleep(1000, controller.signal); // rejects as soon as controller.abort() is called
 */
export async function sleep(ms = 0, signal?: AbortSignal): Promise<void> {
  return new Promise<void>((resolve, reject) => {
    if (signal?.aborted === true) {
      reject(abortReason(signal));
      return;
    }
    const timer = setTimeout(() => {
      signal?.removeEventListener('abort', onAbort);
      resolve();
    }, ms);
    function onAbort(this: AbortSignal): void {
      clearTimeout(timer);
      reject(abortReason(this));
    }
    signal?.addEventListener('abort', onAbort, { once: true });
  });
}
Test file
ts
import { sleep } from './sleep';

describe(sleep, () => {
  beforeEach(() => {
    vi.useFakeTimers();
  });

  afterEach(() => {
    vi.useRealTimers();
  });

  it('resolves after the delay', async () => {
    const onResolved = vi.fn<() => void>();
    async function sleepThenNotify(): Promise<void> {
      await sleep(100);
      onResolved();
    }
    const promise = sleepThenNotify();
    await vi.advanceTimersByTimeAsync(99);
    expect(onResolved).not.toHaveBeenCalled();
    await vi.advanceTimersByTimeAsync(1);
    await promise;
    expect(onResolved).toHaveBeenCalledOnce();
  });

  it('rejects when the signal is aborted, and clears the timer', async () => {
    const controller = new AbortController();
    const promise = sleep(100, controller.signal);
    controller.abort();
    await expect(promise).rejects.toThrow(expect.objectContaining({ name: 'AbortError' }));
    expect(vi.getTimerCount()).toBe(0);
  });

  it('rejects at once when the signal is already aborted', async () => {
    await expect(sleep(100, AbortSignal.abort(new Error('Stopped')))).rejects.toThrow('Stopped');
    expect(vi.getTimerCount()).toBe(0);
  });

  it('wraps a non-error abort reason', async () => {
    const controller = new AbortController();
    const promise = sleep(100, controller.signal);
    controller.abort('cancelled');
    await expect(promise).rejects.toThrow('cancelled');
  });

  it('removes its abort listener once resolved', async () => {
    const controller = new AbortController();
    const removeListener = vi.spyOn(controller.signal, 'removeEventListener');
    const promise = sleep(10, controller.signal);
    await vi.advanceTimersByTimeAsync(10);
    await promise;
    expect(removeListener).toHaveBeenCalledOnce();
  });
});

Keywords: sleep, async, function

Generated from the JSDoc of the sources.