Timers
Timers run a function after a delay or on repeat — debouncing, polling, timeouts, sleeping inside handlers. While a timer waits, the event loop serves other requests; no thread is blocked.
Quick look
setTimeout(fn, ms) runs fn after at least ms milliseconds. Both timers schedule before either fires, so the 50ms one wins despite registering second. console.log("end") runs first because timers never block.
Cancel a timer
setTimeout returns a numeric id; clearTimeout(id) cancels it. Clearing an unknown or expired id is a safe no-op — it never throws. Same pair for intervals: setInterval / clearInterval.
Repeat on an interval
Caution: Always clear intervals you no longer need — a live interval keeps the event loop alive and the process never exits.
Delay inside async code
The standard sleep pattern. It yields to the event loop, so other connections are served during the wait — use it freely inside async HTTP handlers.
Extra arguments pass through (up to 8 after ms):
Zero-delay yield
setTimeout(fn, 0) yields to the event loop's next iteration (the setImmediate role in Node). Negative delays behave the same as 0.
Microtasks vs timers
Microtasks drain before timers. Use queueMicrotask for work that must run before the next timer or I/O event — it is also the replacement for the missing process.nextTick.
Unref: let the process exit
setTimeout/setInterval return a Timeout object (usable as a numeric id too). t.unref() keeps the timer armed but lets the process exit without waiting; t.ref() re-arms the hold; t.hasRef() reports the state; t.refresh() restarts the countdown. Use unref for background housekeeping that must not keep a CLI alive.
Practical example: polling with backoff
Unhealthy stretches back off to 5s; the .unref() keeps the reschedule timer from holding the process open, so once the in-flight fetch settles and nothing else holds the loop, the process ends on its own instead of waiting out the delay.
Reference
Limits: max 128 live timers (the 129th throws TypeError: too many timers), max 8 extra args, negative delay → 0. When no server, work, or referenced timers remain, the loop exits on its own.
Troubleshooting
Process never exits — a live interval or referenced timer holds the loop. clearInterval what you own, or unref housekeeping timers.
too many timers — over 128 live. You leak setIntervals or schedule per-request timers without clearing; reuse or debounce.
Timer fires late under load — delays are minimums. A blocked loop (CPU-heavy handler, 512 full connections) delays everything; move work to Workers.
