# delay: resolving a promise after a given number of milliseconds

> delay is a small ESM package that returns a promise which settles after a given time, with a random range variant, a value to resolve with, AbortSignal cancellation, a clearDelay escape hatch and a factory for injecting your own timer functions. The README is unusually candid that on Node.js alone you can use the built-in timers promise instead, and that this package earns its place on browser support and the extra features.

**sindresorhus/delay** — Delay a promise a specified amount of time

- Repository: https://github.com/sindresorhus/delay
- Stars: 625 · Forks: 37
- Language: JavaScript
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/sindresorhus-delay

## The honest pitch is in the tip at the top

delay delays a promise a specified amount of time. That is the whole product, and the README states it in the first line and repeats it in the description: Delay a promise a specified amount of time.

What makes the README worth reading is the tip immediately after the title. If you target Node.js only, you can use import {setTimeout} from 'node:timers/promises'; await setTimeout(1000); instead, and the package can still be useful if you need browser support or the extra features.

That is a maintainer telling you when not to use their own package, which is unusual and worth taking at face value. On a Node.js only project, the built-in does the same job with no dependency. The reasons to take the package instead are the ones the README lists: browser support, because node:timers/promises is not available there, and the extra features, which are the subject of the rest of this article.

The audience is therefore JavaScript developers writing code that has to run in both environments, plus anyone who needs cancellation, randomisation or timer injection.

The package is MIT licensed, published by Sindre Sorhus, and the metadata shows a funding link to his GitHub sponsors page, which is the standard arrangement for his packages. The repository is small in a way that is itself informative: index.js, index.d.ts, index.test-d.ts, test.js, package.json, a license file, a readme, an .editorconfig and the .github directory. There is no src/ directory, no build configuration and no bundler, because the package ships the file you wrote.

The types are in a separate index.d.ts file rather than being generated, and the published files list confirms the shape of the distribution: index.js and index.d.ts, nothing else.

## Four functions, and the one that is not about waiting

The API is small enough to hold in your head, and the README documents it in four parts.

The default export, delay(milliseconds, options?), creates a promise which resolves after the specified milliseconds. That is the whole of the basic use:

```js
import delay from 'delay';

bar();

await delay(100);

// Executed 100 milliseconds later
baz();
```

rangeDelay(minimum, maximum, options?) creates a promise which resolves after a random number of milliseconds between the two bounds. The README gives the reason, and it is a good one: it is useful for tests and web scraping because they can have unpredictable performance. The example is a test that asserts a method should not take longer than a certain amount of time, which could take longer when it runs on continuous integration, so instead of an exact delay you give a threshold.

clearDelay(delayPromise) clears the delay and settles the promise. The README notes that if you pass in a promise that is already cleared, or a promise coming from somewhere else, it does nothing, which makes it safe to call defensively on a promise you are not sure about.

createDelay({clearTimeout, setTimeout}) creates a new delay instance using the functions you provide for clearing and setting timeouts. The README says it is useful if you are about to stub timers globally but still want to use delay to manage your tests, which is the fourth function and the least obvious of the set.

Two of these functions exist for testability, which is the strongest signal about the intended use. A library that ships a timer injection factory is telling you its users write tests, and that their tests fake timers.

## Resolving with a value, and resolving at a random time

The options object carries two things: a value and a signal.

The value option is typed as unknown, and it is the value the returned promise resolves with. The README's example resolves with a string, and the returned value comes back as the result of awaiting the delay. That is a small feature with a real use: it lets a delay carry a result forward, so an await inside a promise chain can hand a value to whatever comes next, instead of you needing a separate variable to hold it.

The signal option is an AbortSignal, and the returned promise is rejected with an AbortError if the signal is aborted. The example uses an AbortController, aborts it from a timer after 500 milliseconds, and catches the rejection to read error.name, which is AbortError.

This is the difference between a delay you can cancel and one you cannot. A raw setTimeout gives you a handle to clear the timer, but the promise wrapper pattern has no way to say stop, and delay solves that by accepting the same AbortSignal the rest of the platform APIs use. If your code already passes AbortSignals around, that is the argument for using it here rather than rolling your own wrapper.

Combined with rangeDelay, you have three timing behaviours from one package: a fixed wait, a randomised wait inside a window, and a wait you can cancel from the outside. The random one is the one people tend to overlook, and it exists to make timing dependent tests deterministic in the way you want rather than flaky in the way you did not plan.

## Unlimited delay length, which setTimeout cannot promise

There is one capability here that the platform does not provide, and the README flags it explicitly: unlike native setTimeout, this supports unlimited delay length.

The reason is the 32 bit millisecond timer in most JavaScript engines. A delay of more than about 24.8 days overflows and fires immediately, which is not a problem you encounter until you have a job that retries every month, a cron-like worker, or a long backfill. The dependency list explains the fix: unlimited-timeout, which the related packages section describes as setTimeout and setInterval that works with delays longer than 24.8 days.

So delay is not only a promise wrapper. It is a promise wrapper that also fixes the overflow case, which is the kind of thing that is invisible until it bites.

The other runtime dependency is random-int, which backs the rangeDelay function. Both are tiny and both exist for one specific reason, which is a good sign in a package this small: the dependencies are not incidental, they are the two things the platform does badly.

There is a related package in the same family for the other half of the problem. p-timeout is described as a timeout after a specified amount of time, and p-cancelable creates a promise that can be canceled. delay covers the waiting, those two cover the deadline and the cancellation, and the promise-fun collection links them together.

## createDelay and the stubbed timer problem

The fourth function deserves its own look, because it exists to solve a problem that anyone who tests asynchronous code eventually hits.

createDelay({clearTimeout, setTimeout}) creates a new delay instance that uses the functions you hand it. The documented use case is precise: you are about to stub timers globally, but you still want to use delay to manage your tests.

The situation is common. A test suite installs fake timers so that a setTimeout call fires instantly. If delay captured the global setTimeout at import time, or if it uses a timer that your fake does not control, your delay becomes the one thing in the test that does not run under fake time, and the test either hangs or takes a real 100 milliseconds. Injecting the fake timer functions into a delay instance keeps the two worlds consistent.

The example in the README is short:

```js
const customDelay = createDelay({clearTimeout, setTimeout});
```

and then that instance is used exactly like the default one.

The design point is that the injection is per instance rather than global. A library that replaced the global setTimeout would be hostile to every other library in the process. This one hands you a second, separately configured delay, and leaves the global alone.

The dev dependencies support the same story. The test script is xo and node --test and tsd, so the package lints with xo, runs its tests on the Node test runner, and type checks its own type definitions with tsd, and there is a separate index.test-d.ts file for the type level tests.

## An ESM only package for Node 20 and up

The packaging details in the manifest are short and they set the compatibility envelope for adopters.

The package is type module, so it is an ES module only, and the exports map offers the types entry and the default entry with no CommonJS fallback. The engines field requires node >=20, which is a meaningful floor for a package this small and rules out a lot of older production runtimes.

The sideEffects field is false, which tells a bundler that importing the module has no side effects, so a tree shaker can drop it when it is unused. For a package whose entire API is functions, that is exactly right.

The published files are index.js and index.d.ts, and there is no build step in the scripts, only the test command. The source you read in the repository is the source that ships.

The keywords list is a good summary of the intended vocabulary: promise, resolve, delay, defer, wait, stall, timeout, settimeout, event, loop, next, tick, async, await, promises, bluebird, threshold, range, random. The presence of bluebird is a nod to the era when this kind of helper was written for promise libraries, and the presence of threshold and range reflects the rangeDelay addition.

The version history is worth a sentence. v5.0.0 in February 2021, v6.0.0 in May 2023, and v7.0.0 in October 2025, so the major bumps come every couple of years, presumably for the Node version floor and the module format rather than for new features, since the feature list in the README is small. The last push to the repository was on 2026-09-18, which is recent, so the maintenance is a single maintainer shipping occasional releases rather than a frequent cycle.

## Conclusion

delay suits code that runs in the browser as well as on the server and needs a promise that settles later, and it suits test suites that need a cancellable or randomised wait rather than a raw timer. It does not suit a Node.js only project, because the README itself points you at the built-in setTimeout promise from node:timers/promises for that case. Check three things before you add it: that you actually need the extra features rather than a plain timer, that your Node version is 20 or newer because the package requires it, and that a floating promise is acceptable in your codebase, since delay returns a promise that nobody awaits is an unhandled rejection waiting to happen. The newest release is v7.0.0 from 2025-10-31 and the last push to the repository was on 2026-09-18.

## FAQ

### Do I need the delay package on Node.js?

Probably not. The README's tip says that if you target Node.js only you can use import {setTimeout} from 'node:timers/promises' instead, and that the package remains useful if you need browser support or the extra features.

### What functions does the delay package export?

The default export delay(milliseconds, options?), plus rangeDelay(minimum, maximum, options?), clearDelay(delayPromise) and createDelay({clearTimeout, setTimeout}). The range variant resolves after a random number of milliseconds between the two bounds, and clearDelay settles the promise.

### How do I cancel a delay?

Pass an AbortSignal in the options. The returned promise is then rejected with an AbortError when the signal is aborted, as shown in the README example using an AbortController. The clearDelay function is the other way to settle a delay early.

### Why use rangeDelay in a test?

The README describes it as useful for tests and web scraping because they can have unpredictable performance, so instead of asserting an exact delay you can give a threshold. A test that should not take longer than a certain time could take longer on continuous integration, and a random delay inside a window avoids that.

### What are the requirements for the delay package?

It is an ESM only package requiring Node.js 20 or newer, published under the MIT licence. It has two runtime dependencies, random-int for the range variant and unlimited-timeout, which allows delays longer than the usual setTimeout limit of about 24.8 days.

## Sources

- [Issues](https://github.com/sindresorhus/delay/issues)
- [License: MIT](https://github.com/sindresorhus/delay/blob/main/LICENSE)
- [README](https://github.com/sindresorhus/delay/blob/main/README.md)
- [Releases](https://github.com/sindresorhus/delay/releases)
- [sindresorhus/delay on GitHub](https://github.com/sindresorhus/delay)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sindresorhus-delay
