mrkeyoor.com_
Sat 08 Aug 17:39 UTC
npmUtilsupdated 08 Aug 2026

delay

Tiny promise-based timer utility for JavaScript. Calling delay(milliseconds) returns an awaitable promise; options can carry a resolved value or an AbortSignal. Version 7 also exposes a random range delay, a way to settle a pending delay early, and a factory that accepts custom timer functions for tests. Its unusual feature is support for waits beyond the native timer limit. It works in browsers and modern Node, but Node-only code already has a standard promise timer.

Verdict

A clean browser-and-Node timer helper with useful cancellation and test hooks. Skip it in Node-only applications unless you specifically need range delays, early settlement, custom timers, or waits beyond native timeout limits.

API stability4/5The default delay(milliseconds, options) contract is deliberately small and its options remain value and signal. Named exports add features without complicating the basic call. The major-version risk is runtime packaging rather than function shape: version 7 requires Node 20 and ESM, so an upgrade can break CommonJS or older deployment environments even when source calls look unchanged.
Docs5/5The README documents every export with short examples, types, cancellation behavior, unlimited waits, and the recommendation to use node:timers/promises for Node-only code. The shipped declaration file mirrors that surface. There is little troubleshooting or runtime discussion, but the API is small enough that the concise documentation covers nearly every legitimate use.
Maintenance4/5Version 7.0.0 was released and the repository was pushed on October 31, 2025. That release intentionally raised the Node minimum and added unlimited delays. GitHub metadata currently reports no open issues or pull requests. The project is quiet because its scope is narrow, though consumers should expect new majors to follow the maintainer's modern-runtime policy.
Ecosystem4/5The package recorded 6,813,136 downloads in the measured week despite exposing only a few functions. It belongs to a well-known family of focused promise utilities and works in browser and Node ESM projects with built-in types. Its ecosystem value is convenience and familiarity, not integrations; native Node timers remove much of the reason to add it server-side.

Use it if

  • One code path must use the same cancellable promise delay in both browsers and Node
  • You need delays longer than the native timer ceiling and want the package to schedule them safely
  • Tests need an injectable timer implementation while application code keeps the same delay API
  • You need rangeDelay or clearDelay often enough that a small shared helper is preferable to repeating it
Skip it if

Setup reality

npm install delay is simple, and TypeScript declarations ship through the package export. The version 7 boundary matters: it requires Node 20 or newer and the package is ESM, so CommonJS require('delay') is not the supported import shape. Node-only projects probably should not install it because node:timers/promises already provides an awaitable setTimeout with value and AbortSignal options. Browser projects must have AbortController support if they use cancellation. An aborted delay rejects with an AbortError; forgetting to catch that rejection can become an unhandled rejection during shutdown or component cleanup. clearDelay does not reject or cancel the promise in the usual sense: the README says it clears the timer and settles the promise, so awaiting code proceeds with its configured value. It also silently does nothing for unrelated promises. rangeDelay adds nondeterministic timing and should not appear in ordinary unit tests unless that behavior is the subject of the test. createDelay is the escape hatch for fake timers, but pass a matched setTimeout and clearTimeout pair from the same clock implementation. Unlimited delays are implemented through a dependency rather than one native timeout, so they are scheduling convenience, not durable jobs; process restarts, suspended tabs, and machine sleep can still invalidate wall-clock expectations. Use a job queue or persisted timestamp for anything operationally important.

Patterns

Wait before continuingpause-async-flow

import delay from 'delay';

await delay(250);
await refreshStatus();

For Node-only code, prefer setTimeout from node:timers/promises unless another delay feature is needed.

Resolve with a typed valueresolve-with-value

import delay from 'delay';

const state = await delay(100, { value: 'ready' });
console.log(state);

The promise resolves to the exact value, and TypeScript infers its type from the option.

Cancel a pending waitabort-delay

import delay from 'delay';

const controller = new AbortController();
const waiting = delay(10_000, { signal: controller.signal });
controller.abort();

try {
  await waiting;
} catch (error) {
  if (error.name !== 'AbortError') throw error;
}

Aborting rejects with AbortError; always handle that expected rejection.

Stop on cancellation or deadlinecombine-abort-signals

import delay from 'delay';

const signal = AbortSignal.any([
  request.signal,
  AbortSignal.timeout(5_000),
]);
await delay(1_000, { signal });

AbortSignal.any and AbortSignal.timeout require current runtimes; this cancels the wait, not unrelated application work.

Wait for a random intervalrandomize-wait

import { rangeDelay } from 'delay';

await rangeDelay(500, 1_500);

Random waits make tests nondeterministic. Use this for deliberate jitter, not as a substitute for synchronization.

Settle a delay earlyclear-pending-delay

import delay, { clearDelay } from 'delay';

const pending = delay(30_000, { value: 'continue' });
clearDelay(pending);
console.log(await pending);

clearDelay settles rather than rejects the promise, so awaiting code continues immediately.

Create a delay bound to test timersinject-fake-timers

import { createDelay } from 'delay';

const testDelay = createDelay({
  setTimeout: clock.setTimeout.bind(clock),
  clearTimeout: clock.clearTimeout.bind(clock),
});

const pending = testDelay(1_000, { value: 'done' });
clock.tick(1_000);
await pending;

Pass both timer functions from the same fake clock or clearDelay cannot reliably clear scheduled work.

Back off between bounded retriesretry-with-backoff

import delay from 'delay';

for (let attempt = 0; attempt < 4; attempt++) {
  try {
    return await request();
  } catch (error) {
    if (attempt === 3 || !isRetryable(error)) throw error;
    await delay(250 * 2 ** attempt, { signal });
  }
}

Retry only idempotent operations or use an idempotency key; the timer does not make a repeated side effect safe.

Spread concurrent retry attemptsadd-retry-jitter

import { rangeDelay } from 'delay';

const cap = Math.min(10_000, 300 * 2 ** attempt);
await rangeDelay(0, cap, { signal });

Full jitter reduces synchronized retries, but server Retry-After guidance should take priority when present.

Avoid the dependency in Node-only codeuse-native-node-timer

import { setTimeout as delay } from 'node:timers/promises';

await delay(250, 'ready', { signal });

This is the README's recommended Node-only alternative and supports both a resolved value and AbortSignal.

Alternatives

PackageRegistryPick it when
sleep-promisenpmYou need only a minimal promise sleep and do not need AbortSignal, random ranges, or custom timers
p-sleepnpmA very small legacy-compatible sleep helper is enough and its older release profile fits your runtime
promise-timeoutnpmYour actual problem is rejecting work that exceeds a deadline rather than pausing before the next step