timersPromises.setTimeout()
Returns a promise that fulfills with a value after the given delay, cancellable with an AbortSignal.
timersPromises.setTimeout([delay[, value[, options]]])- Added in Node.js v15.0.0
Parameters
| Parameter | Type | Description |
|---|---|---|
$delayoptional | <number>= 1 | The number of milliseconds to wait before fulfilling the promise. |
$valueoptional | <any> | A value with which the promise is fulfilled. |
$optionsoptional | <Object> | Options controlling the scheduled Timeout. |
$options.refoptional | <boolean>= true | Set to false to indicate that the scheduled Timeout should not require the Node.js event loop to remain active. |
$options.signaloptional | <AbortSignal> | An optional AbortSignal that can be used to cancel the scheduled Timeout. |
Return value
<Promise>A promise fulfilled with value after delay milliseconds, or rejected with an AbortError if the signal is aborted.
Errors
Rejects with an AbortError (code ABORT_ERR) if options.signal is aborted, and throws ERR_INVALID_ARG_TYPE for a non-AbortSignal signal.
Changelog
- PHP v16.0.0The node:timers/promises API graduated from experimental to stable.
Imported from node:timers/promises, this is the promisified setTimeout() — the sleep helper most codebases used to hand-roll, with cancellation built in.
const { setTimeout: sleep } = require('node:timers/promises');
await sleep(100);When to reach for it
- Backoff between retries inside an
asyncfunction. - Pacing a loop that must not hammer an API.
- Any wait that should be abortable when a request is cancelled or the process is shutting down.
What trips people up
Because the callback version and this one share a name, importing carelessly shadows the global. Alias it (setTimeout: sleep) so the rest of the file still gets the callback form it expects. Note also that the argument order is inverted relative to the global: the delay comes first and there is no callback.
Cancellation rejects rather than resolves. An aborted wait throws an AbortError with code ABORT_ERR, so it must be caught — an unhandled one crashes the process. Aborting a signal that is already settled does nothing.
The returned promise's timer is ref'd by default and will hold the process open for the full delay. Pass { ref: false } for background waits, remembering that the promise may then simply never settle because the process exits first.
Example
When to use it
- Wait between retry attempts with exponential backoff
- Throttle a batch loop to stay under an API rate limit
- Give a graceful shutdown a bounded drain window
- Abort a pending wait when the incoming request is cancelled
Frequently asked questions
How do I sleep or pause inside an async function in Node?
Import setTimeout from node:timers/promises and await it: await setTimeout(1000). It replaces the hand-written new Promise(r => setTimeout(r, ms)) wrapper and adds AbortSignal support.
How do I cancel an awaited delay?
Pass an AbortSignal in the options: await setTimeout(1000, undefined, { signal: ac.signal }). Calling ac.abort() rejects the promise with an AbortError whose code is ABORT_ERR, so wrap the await in a try/catch.
Does the promise version of setTimeout keep my process alive?
Yes by default, for the full duration of the delay. Pass { ref: false } to release that hold — but be aware the promise may then never settle, because Node can exit before the timer fires.
More examples
Retry with backoff and a shared abort signal
The same signal cancels both the in-flight fetch and the sleep between attempts, so an abort takes effect immediately instead of waiting out the current backoff. The AbortError is rethrown rather than retried, which is what distinguishes 'the caller cancelled' from 'the request failed'.
import { setTimeout as sleep } from 'node:timers/promises';
async function withRetry(fn, { attempts = 5, base = 200, signal } = {}) {
for (let i = 0; i < attempts; i++) {
try {
return await fn({ signal });
} catch (err) {
if (err.name === 'AbortError') throw err; // caller gave up
if (i === attempts - 1) throw err;
const wait = base * 2 ** i;
console.log(`attempt ${i + 1} failed (${err.message}); retrying in ${wait}ms`);
await sleep(wait, undefined, { signal }); // abortable backoff
}
}
}
const ac = new AbortController();
setTimeout(() => ac.abort(), 5000); // global 5s budget
const body = await withRetry(
({ signal }) => fetch('https://example.com/api', { signal }).then((r) => r.text()),
{ signal: ac.signal },
);
console.log(body.slice(0, 40));Racing an operation against a deadline
Prints 'failed: exceeded 100ms'. The abort in finally cancels the deadline's own timer, which is what matters when the real work wins the race — otherwise that timer stays pending for the rest of ms and holds the event loop open after the result is known. It cannot touch the losing operation: the ref'd sleep(5000) stored in slow has no signal of its own, so this snippet still sits idle for the remaining ~5 seconds before the process exits. Give the operation its own AbortSignal, or create it with { ref: false }, if exit should follow the deadline immediately.
import { setTimeout as sleep } from 'node:timers/promises';
async function deadline(promise, ms) {
const ac = new AbortController();
const timer = sleep(ms, 'timeout', { signal: ac.signal });
try {
const winner = await Promise.race([promise, timer]);
if (winner === 'timeout') throw new Error(`exceeded ${ms}ms`);
return winner;
} finally {
ac.abort(); // cancel the loser so the loop is not held open
}
}
const slow = sleep(5000, 'finished eventually');
try {
console.log(await deadline(slow, 100));
} catch (err) {
console.log('failed:', err.message);
}A background wait that must not delay exit
Demonstrates the trade-off in { ref: false }: the process is free to exit, but everything after the await is then simply abandoned. Use it for opportunistic work, never for anything whose completion you rely on.
import { setTimeout as sleep } from 'node:timers/promises';
async function warmCacheLater() {
// ref: false -> this wait alone will not keep Node running.
await sleep(60_000, undefined, { ref: false });
console.log('cache warmed');
}
warmCacheLater();
console.log('main work done');
// Output: only 'main work done'. The process exits immediately
// and the promise above never settles.
Discussion