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.
Recipe
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:
- Retry backoff in outbound HTTP clients
- Debouncing cache refresh triggers
- Polling job status until terminal state
- Jittered delays in integration tests
Working Example
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:
- Exponential backoff with
timers/promisessleep AbortSignalcancels pending timer withAbortErrorsetIntervalasync iterator (Node timer promises API) for periodic async loops- Prefer queue schedulers for distributed cron
Deep Dive
How It Works
- Callback timers live in
node:timers- macrotask queue in libuv timers phase. - timers/promises wraps them as Promises and async iterables.
ref/unrefon Timeout handles - unref allows process exit if only timers remain.- scheduler module (browser-oriented) has limited Node server use cases.
API Pick
| API | Style |
|---|---|
setTimeout(cb, ms) | Callback |
setTimeout(ms) from promises | await sleep |
setInterval iterator | async periodic |
| BullMQ / cron | production schedules |
TypeScript Notes
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 });
}
}Gotchas
- setInterval with slow async callback - overlapping runs. Fix: chain
setTimeoutafter await completes. - Not clearing timers on shutdown - delays SIGTERM exit. Fix:
clearTimeoutin shutdown handler. - Drift under load - not wall-clock accurate. Fix: external cron for midnight jobs.
- Thousands of active timers - heap and timer phase cost. Fix: batch or single coordinator timer.
- Forgetting unref on background heartbeat - blocks deploy exit. Fix:
timeout.unref()when appropriate.
Alternatives
| 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 |
FAQs
timers/promises vs callbacks?
Promises for async/await code; callbacks fine for event-style APIs.
How to cancel setTimeout?
clearTimeout(handle) or AbortSignal with promises variant.
What is timer.unref?
Timer won't keep process alive - useful for optional background ticks.
setImmediate vs setTimeout 0?
Different libuv phases - see Microtasks vs Macrotasks.
Accuracy for SLA timers?
Event loop lag adds jitter - not for hard real-time guarantees.
setInterval async iterator errors?
Break loop with break/return; handle AbortSignal for shutdown.
NestJS @Cron?
Uses node-cron under hood - separate from raw timers module.
Tests and fake timers?
Vitest fake timers mock timer APIs - use for deterministic tests.
Maximum delay?
32-bit signed limit ~24.8 days for some timer internals - use Date scheduling for longer.
Drift monitoring?
Compare intended vs actual interval with performance.now() logs.
cluster and timers?
Each worker runs own timers - duplicate cron unless leader election.
Relation to Timers & Scheduling event-loop page?
Timers & Scheduling covers event loop interaction depth.
Related
- Timers & Scheduling - drift and phases
- Built-in APIs Basics - overview
- BullMQ ioredis - distributed scheduling
- Event Loop Best Practices - avoid timer abuse
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.