Yieldless
Reference

yieldless/timer

Abort-aware sleep and polling helpers built on native timers.

yieldless/timer covers the small timing jobs that show up around network and UI work: wait for a little while, make that wait cancelable, or poll a tuple-returning operation until it is ready.

It is deliberately not a scheduler. Timers stay native, cancellation stays on AbortSignal, and polling returns the same tuple shape as the rest of Yieldless.

Exports

  • sleep(delayMs, options): Promise<void>
  • sleepSafe(delayMs, options): Promise<SafeResult<void, E>>
  • poll(operation, options): Promise<SafeResult<T, E>>
  • type PollOperation<T, E = Error> = (attempt, signal) => SafeResult<T, E> | PromiseLike<SafeResult<T, E>>
  • type PollOptions<E = Error> = { intervalMs, maxAttempts?, timeoutMs?, signal?, shouldContinue? }

Sleep

import { sleep } from "yieldless/timer";

await sleep(250, { signal });

If the signal aborts, sleep() rejects with the abort reason. Use sleepSafe() when the wait itself belongs in tuple form.

import { sleepSafe } from "yieldless/timer";

const [error] = await sleepSafe(250, { signal });

Poll

import { poll } from "yieldless/timer";

const [error, job] = await poll(
  async (_attempt, signal) => getJob(jobId, signal),
  {
    intervalMs: 1_000,
    timeoutMs: 30_000,
    signal,
  },
);

poll() stops when the operation returns [null, value], when maxAttempts is reached, when shouldContinue() returns false, or when cancellation/timeout aborts the derived signal.

Behavior notes

  • sleep(0) resolves immediately and is useful as an async yield point.
  • Negative or non-finite delays throw RangeError.
  • Poll attempts start at 1.
  • timeoutMs uses yieldless/signal internally, so timeout errors are TimeoutError.
  • The operation receives the same signal that controls the interval wait.

Good

Use poll() for eventual consistency and job status checks.

const [error, job] = await poll(
  async (_attempt, signal) => {
    const [error, current] = await fetchJsonSafe<Job>(jobUrl, { signal });
    if (error) return [error, null];

    return current.state === "ready"
      ? [null, current] as const
      : [new Error("Job is not ready"), null] as const;
  },
  {
    intervalMs: 1_000,
    timeoutMs: 30_000,
    signal,
  },
);

Use sleepSafe() when a delay is part of tuple-native control flow.

Avoid

Do not write uncancelable timer promises.

await new Promise((resolve) => {
  setTimeout(resolve, 1_000);
});

If a user can navigate away or a request can abort, pass the signal to sleep() or poll().

On this page