Timers and Scheduler
Node timers schedule macrotasks on the event loop - node:timers callbacks and node:timers/promises async sleeps power retries, debouncing, and polling without extra dependencies.
Search across all documentation pages
Node timers schedule macrotasks on the event loop - node:timers callbacks and node:timers/promises async sleeps power retries, debouncing, and polling without extra dependencies.
import { setTimeout, setInterval } from 'node:timers/promises';
await setTimeout(500, undefined, { signal: abortController.signal });
for await (const start of setInterval(1000, Date.now())) {
console.log('tick', Date.now() - start);
}When to reach for this:
import { setTimeout as sleep } from 'node:timers/promises';
async function retry<T>(fn: () => Promise<T>, attempts = 3): Promise<T> {
let lastErr: unknown;
for (let i = 0; i < attempts; i++) {
try {
return await fn();
} catch (err) {
lastErr = err;
if (i < attempts - 1) {
await sleep(100 * 2 ** i);
}
}
}
throw lastErr;
}
const controller = new AbortController();
const cancellable = sleep(10_000, undefined, { signal: controller.signal })
.catch((err) => console.log('cancelled', err.name));
controller.abort();
await cancellable;What this demonstrates:
timers/promises sleepAbortSignal cancels pending timer with AbortErrorsetInterval async iterator (Node timer promises API) for periodic async loopsnode:timers - macrotask queue in libuv timers phase.ref/unref on Timeout handles - unref allows process exit if only timers remain.| API | Style |
|---|---|
setTimeout(cb, ms) | Callback |
setTimeout(ms) from promises | await sleep |
setInterval iterator | async periodic |
| BullMQ / cron | production schedules |
import { setTimeout } from 'node:timers/promises';
export async function pollUntil(
check: () => boolean | Promise<boolean>,
{ intervalMs = 200, timeoutMs = 5_000, signal }: {
intervalMs?: number;
timeoutMs?: number;
signal?: AbortSignal;
} = {},
): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (!(await check())) {
if (Date.now() > deadline) throw new Error('timeout');
await setTimeout(intervalMs, undefined, { signal });
}
}setTimeout after await completes.clearTimeout in shutdown handler.timeout.unref() when appropriate.| Alternative | Use When | Don't Use When |
|---|---|---|
node-cron / K8s CronJob | Wall-clock schedules | Sub-second in-process |
| BullMQ delayed jobs | Distributed retries | Simple local debounce |
queueMicrotask | Defer after stack | Time-based delay |
performance.now deadlines | Measure elapsed | Schedule far future job |
Promises for async/await code; callbacks fine for event-style APIs.
clearTimeout(handle) or AbortSignal with promises variant.
Timer won't keep process alive - useful for optional background ticks.
Different libuv phases - see Microtasks vs Macrotasks.
Event loop lag adds jitter - not for hard real-time guarantees.
Break loop with break/return; handle AbortSignal for shutdown.
Uses node-cron under hood - separate from raw timers module.
Vitest fake timers mock timer APIs - use for deterministic tests.
32-bit signed limit ~24.8 days for some timer internals - use Date scheduling for longer.
Compare intended vs actual interval with performance.now() logs.
Each worker runs own timers - duplicate cron unless leader election.
Timers & Scheduling covers event loop interaction depth.
Stack versions: This page was written for Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5, and NestJS 11.
Reviewed by Chris St. John·Last updated Jul 16, 2026