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.
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
| Install | ✓ · 1s | 3 packages on disk · 1 MB |
| Import | ✓ | ESM import works · require() works · ESM package with exports map |
| Browser | 0.9 KB | gzipped (1.6 KB minified), bundled with esbuild |
| Types | ✓ | TypeScript types bundled |
| Known vulns | 0 | 0 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.
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.
- 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.
- Your production runtime is Node 18. Version 7.0.0 declares Node 20 as its minimum, so installing an older major leaves you on a different support line.
- You need a deadline around network or database work. delay only controls its timer; it cannot stop an operation that ignores AbortSignal.
- A scheduled action must survive restarts, laptop sleep, or a closed browser tab. These timers keep no durable job state.
- One short browser sleep is the entire requirement. A local Promise around setTimeout may be clearer than adding 2 direct dependencies.
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
| Package | Registry | Pick it when |
|---|---|---|
| p-timeout | npm | Choose it to reject, cancel, or fall back when another promise exceeds a deadline. |
| p-min-delay | npm | Choose it when existing work must remain pending for at least a set duration. |
| promise-timeout | npm | Choose 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.

