mrkeyoor.com_
Tue 22 Sept 18:47 UTC
npmUtilsupdated 22 Sept 2026

delay review

`delay` is a promise-based timer for JavaScript. Besides waiting a fixed number of milliseconds, version 7.0.0 can return a value, reject through an `AbortSignal`, choose a random delay, finish a package-created wait early, and handle durations above the native timer ceiling. It now requires Node 20. The package makes the most sense when the same timer code runs in Node and a browser, since its own README points Node-only projects to `node:timers/promises` for ordinary sleeps.

Verdict

delay 7.0.0 installed in 1 second, used 1 MB across 3 packages, and produced a 0.9 KB gzipped browser bundle with no audit findings in our sandbox. Install it for cross-runtime cancellation, random waits, or early settlement; use Node's built-in promise timer for a plain server-side sleep.

We installed it

Lab card: what happened when we installed delayScreenshot of delay documentation
Install✓ · 1s3 packages on disk · 1 MB
ImportESM import works · require() works · ESM package with exports map
Browser0.9 KBgzipped (1.6 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does delay install cleanly?

Yes. In a fresh container with an empty cache, npm install delay finished in 1 seconds, leaving 3 packages and 1 MB on disk. npm audit reported no known vulnerabilities.

How much does delay add to a browser bundle?

0.9 KB gzipped (1.6 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does delay work with both ESM and CommonJS?

Yes. Both import 'delay' and require('delay') worked in Node 22 in our run. The package is published as ESM with an exports map.

Does delay include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

delay or p-timeout: which should you use?

p-timeout: Choose it to reject, cancel, or fall back when another promise exceeds a deadline. delay 7.0.0 installed in 1 second, used 1 MB across 3 packages, and produced a 0.9 KB gzipped browser bundle with no audit findings in our sandbox.

When should you not use delay?

The code runs only on Node 20 or newer and only sleeps. node:timers/promises handles a value and AbortSignal without a dependency, and the README recommends it.

API stability4/5The default `delay(milliseconds, options)` call is still the center of the package, and the extra behaviors live in named exports instead of changing that call shape. Version 7.0.0 adds unlimited durations but also raises the engine requirement to Node 20. Earlier major changes included a move to ESM and removal of a rejection helper, so major upgrades deserve a packaging and API check even though normal waits remain simple.
Docs5/5The README gives a code sample and type information for the default function, rangeDelay(), clearDelay(), createDelay(), the value option, and AbortSignal. It states that abort rejects with AbortError and explains what happens when clearDelay receives an unrelated promise. It also tells Node-only users about the built-in timer up front. Durable scheduling and process suspension are outside its scope, but the package API itself is covered.
Maintenance4/5GitHub shows an active, unarchived repository with 624 stars, 0 open issues and pull requests in the combined counter, and its last push on October 31, 2025. The 7.0.0 package was published on the same date. A timer wrapper does not need weekly releases, but its infrequent majors have carried real runtime policy changes, including the current Node 20 floor.
Ecosystem4/5npm recorded 7,151,102 downloads for August 18 through August 24, 2026. The package supplies TypeScript declarations, an exports map, and browser-compatible code, while our Node 22 checks loaded it through both import and require. Its ecosystem role is narrow by design. Node already covers the common server case, and integrations are mostly ordinary promise composition rather than framework plugins.

Use it if

  • A browser and Node service should share one promise timer API with AbortSignal support.
  • Your test setup needs a delay factory connected to its own setTimeout and clearTimeout functions.
  • A scraper or retry loop needs bounded random jitter through rangeDelay().
  • Code must finish a known pending delay early or wait longer than the native timer limit inside one process.
Skip it if

Setup reality

Our install of delay 7.0.0 completed in 1 second in a clean Node 22 Bookworm container. It left 3 packages occupying 1 MB. The published package has 2 direct dependencies, no peers, and 24 KB unpacked. npm audit reported 0 known vulnerabilities. Bundled TypeScript declarations are present. Our full-package browser import built to 1.6 KB minified and 0.9 KB gzipped.

Version 7.0.0 sets node >=20, declares ESM, and publishes an exports map. Both ESM import and CommonJS require worked on our Node 22 box. Treat that require result as runtime-specific compatibility rather than permission to ignore the engine field. Browsers need AbortController only for the cancellation option.

Cancellation rejects the wait with an AbortError. clearDelay() takes another path: it settles a delay promise early with its configured value. It ignores promises that did not come from this package and calls on an already-cleared wait. rangeDelay() samples between two bounds, so using it in an assertion can introduce timing noise unless randomness is the behavior under test.

For fake clocks, createDelay() needs setTimeout and clearTimeout from the same clock implementation. A duration beyond the native limit is split into workable timers, but the state remains in memory. Store a real deadline or enqueue a job when a wait represents billing, delivery, or another action that must outlive the process.

Patterns

Pause an async function pause-async-function

import delay from 'delay';

await delay(250);
await pollAgain();

Node-only code can import setTimeout from node:timers/promises for this exact job.

Resolve with a value return-delayed-value

import delay from 'delay';

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

The bundled declarations infer the resolved type from the value option.

Abort a pending wait cancel-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 the delay with AbortError; it does not cancel unrelated work.

Give a wait a deadline set-wait-deadline

import delay from 'delay';

await delay(30_000, {
  signal: AbortSignal.timeout(5_000),
});

At 5 seconds this rejects with AbortError, so expected cancellation needs a catch path.

Wait within a range randomize-wait

import {rangeDelay} from 'delay';

await rangeDelay(400, 1_200, {signal});

The selected duration is random between the supplied bounds.

Settle a delay before its timer finish-wait-early

import delay, {clearDelay} from 'delay';

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

clearDelay resolves a package-created wait with its value instead of rejecting it.

Create a timer for tests use-fake-clock

import {createDelay} from 'delay';

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

const waiting = testDelay(1_000);
clock.tick(1_000);
await waiting;

Both timer functions must belong to the same fake clock, with any required receiver binding preserved.

Back off between retries retry-with-backoff

import delay from 'delay';

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

A timer cannot make a repeated write safe. Limit retries to idempotent work or use an idempotency key.

Spread concurrent retries jitter-retries

import {rangeDelay} from 'delay';

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

Use a server-provided Retry-After value when one is available.

Wait beyond the native timer ceiling sleep-past-native-limit

import delay from 'delay';

await delay(30 * 24 * 60 * 60 * 1_000);

Version 7 supports this duration inside one live process; the timer does not survive a restart.

Avoid the dependency in Node use-node-builtin

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

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

The package README recommends this built-in for Node-only waits that do not need delay's extra exports.

Pause between streamed items pace-async-iterator

import delay from 'delay';

for await (const item of source) {
  await send(item);
  await delay(100, {signal});
}

This spaces each start by a fixed amount; it is not a throughput-aware rate limiter.

Alternatives

PackageRegistryPick it when
p-timeoutnpmChoose it to reject, cancel, or fall back when another promise exceeds a deadline.
p-min-delaynpmChoose it when existing work must remain pending for at least a set duration.
promise-timeoutnpmChoose it for a small CommonJS timeout wrapper around an existing promise.

More utils guides

lru-cache · ajv · type-fest · p-limit · find-up · js-yaml · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.