timeout.unref()
Releases a timer's hold on the event loop so the process can exit before it fires.
timeout.unref()- Added in Node.js v0.9.1
Return value
<Timeout>A reference to timeout, so the call can be chained.
Errors
Does not throw; calling it more than once has no effect.
Node exits when nothing is left keeping the event loop alive. Every Timeout from setTimeout() or setInterval() counts as such a handle by default. timeout.unref() removes it from that count: the timer still fires if the loop happens to be running when its deadline arrives, but it will no longer hold the process open on its own.
When to reach for it
- Library-owned background work — heartbeats, cache sweeps, metrics flushes — that must never delay a consumer's shutdown.
- Long watchdogs and idle timeouts that exist only as a safety net.
- Test helpers, so a forgotten timer does not make the suite hang.
What trips people up
Unref'ing is not cancelling. If something else keeps the loop alive, the callback still runs at its scheduled time — an unref'd interval attached to a running HTTP server ticks exactly as before. Use clearTimeout()/clearInterval() when you want the work to stop.
The mirror image is the surprise nobody expects: an unref'd deadline in a process with nothing else to do never fires at all, because Node exits first. A timeout guard that is the only pending handle silently does nothing.
timeout.ref() puts the handle back in the count, and timeout.hasRef() reports the current state. Repeat calls are no-ops.
Example
When to use it
- Keep a library's heartbeat interval from blocking the host process's exit
- Arm a long idle watchdog on a connection without delaying shutdown
- Let a CLI finish immediately once its real work is done
- Stop background timers from hanging a test runner
Frequently asked questions
What is the difference between unref() and clearTimeout()?
unref() only stops the timer from keeping the event loop alive; the callback still runs on schedule if anything else keeps the process going. clearTimeout() cancels the timer outright so the callback never runs.
Why does my unref'd timer never fire?
Because nothing else was keeping the event loop alive, so Node exited before the deadline arrived. An unref'd timer only fires if some other handle — a server, a socket, another ref'd timer — is still holding the loop open.
Can I undo unref()?
Yes, call timeout.ref() to put the timer back into the event loop's reference count, and timeout.hasRef() to check the current state. Repeated calls to either method are no-ops.
More examples
Toggling a timer back on with ref()
unref() and ref() flip the same flag and hasRef() (v11.0.0) reads it. Because ref() was called before the process could exit, the timer still fires — had it stayed unref'd with nothing else pending, Node would have exited and 'fires' would never appear.
const t = setTimeout(() => console.log('fires'), 20);
t.unref();
console.log('hasRef after unref:', t.hasRef()); // false
t.ref();
console.log('hasRef after ref:', t.hasRef()); // true
// Prints:
// hasRef after unref: false
// hasRef after ref: true
// firesA pool that does not outlive its consumer
unref() returns the Timeout, so it chains directly onto setInterval(). Note that close() still calls clearInterval(): while the app is running the reaper keeps ticking, and unref() only affects whether it delays exit.
class ConnectionPool {
constructor(reapEveryMs = 30_000) {
this.idle = [];
// Reap idle connections periodically, but never be the reason
// the host application cannot exit.
this.reaper = setInterval(() => this.reap(), reapEveryMs).unref();
}
reap() {
const before = this.idle.length;
this.idle = this.idle.filter((c) => Date.now() - c.lastUsed < 60_000);
console.log(`reaped ${before - this.idle.length} idle connections`);
}
async close() {
clearInterval(this.reaper); // unref alone would not stop it
await Promise.all(this.idle.map((c) => c.end()));
}
}Why an unref'd deadline can silently never fire
This is the failure mode people report as 'my promise never rejects'. Unref'ing a guard means the process is free to exit while that guard is the only pending work, so the rejection never happens — deadlines you intend to act on should stay ref'd and be cleared on success instead.
function readWithDeadline(stream, ms) {
return new Promise((resolve, reject) => {
const chunks = [];
const guard = setTimeout(() => {
reject(new Error(`stalled for ${ms}ms`));
}, ms);
guard.unref(); // <-- looks harmless, is not
stream.on('data', (c) => chunks.push(c));
stream.on('end', () => {
clearTimeout(guard);
resolve(Buffer.concat(chunks));
});
});
}
// If the stream stalls and nothing else holds the loop open,
// Node exits before the guard fires: no rejection, no error,
// and the promise never settles. Drop the unref() on deadlines
// that must be observed.
Discussion