Node.js 定时器的 ref 和 unref 有什么作用?
简化版
Node.js 定时器默认会让事件循环保持活跃,进程不会退出。调用 unref() 可以表示这个定时器不应该单独阻止进程退出;ref() 则恢复保活能力。它常用于后台心跳、超时兜底和非关键定时任务。
详细版
普通定时器会让 Node 进程继续运行。
const timer = setInterval(() => {
console.log('heartbeat')
}, 10_000)
timer.unref()
unref() 后,如果进程里没有其他活跃任务,Node 可以退出,不会为了这个定时器硬撑着。面试要注意:unref() 不是取消定时器,定时器在进程仍活着时仍可能触发;取消要用 clearTimeout 或 clearInterval。
完整版教学
一、Node 进程退出取决于事件循环是否还有活跃句柄
Node.js 不是代码执行完就一定退出。 只要事件循环里还有活跃句柄,比如 server、socket、定时器,进程就会继续运行。 定时器默认就是会保活的句柄。
setTimeout(() => {
console.log('10 秒后执行')
}, 10_000)
上面代码即使没有其他任务,进程也会等 10 秒。
因为这个 timeout 仍然被认为是活跃工作。
这就是理解 ref/unref 的基础。
二、unref 表示不要为了它单独保活
timer.unref() 的含义是:如果事件循环里只剩这个定时器,不要为了它阻止进程退出。
它适合非关键后台任务。
进程还活着时,定时器仍会正常触发。
const timer = setTimeout(() => {
console.log('兜底清理')
}, 30_000)
timer.unref()
| 操作 | 是否取消定时器 | 是否保活进程 |
|---|---|---|
| 默认定时器 | 否 | 是 |
unref() | 否 | 否 |
ref() | 否 | 是 |
clearTimeout() | 是 | 否 |
这个表能防止一个常见误会:unref 不等于 clear。
它只影响进程退出判断。
三、ref 可以恢复保活
调用 unref() 后,如果业务又需要这个定时器保持进程,可以调用 ref()。
这在动态任务管理中偶尔有用。
不过日常更常见的是只对后台兜底 timer 调一次 unref()。
const timer = setInterval(syncMetrics, 60_000)
timer.unref()
// 某些场景下重新要求保活
timer.ref()
如果一个 CLI 工具只为了上报指标设置了 interval,默认保活会导致命令迟迟不退出。
这类场景用 unref() 很合适。
四、超时兜底常搭配 unref
优雅停机、请求取消、任务超时都可能设置兜底定时器。
兜底定时器的目的不是让进程多活,而是在进程还活着时防止流程卡死。
所以经常会 unref()。
const shutdownTimer = setTimeout(() => {
console.error('shutdown timeout')
process.exit(1)
}, 30_000)
shutdownTimer.unref()
有其他活跃任务 -> 到点触发兜底
没有其他活跃任务 -> 进程可直接退出
这样既有保险,又不让保险本身变成进程退出的阻碍。
五、服务端心跳和指标上报要谨慎使用
后台指标上报、健康心跳、缓存刷新常用 interval。 如果这些任务不是服务存活的必要条件,可以 unref。 但如果它们承担核心调度功能,就不能随意 unref。
| 任务 | 是否适合 unref | 原因 |
|---|---|---|
| CLI 指标上报 | 适合 | 不应阻止命令退出 |
| 停机超时兜底 | 适合 | 只是保险 |
| 核心任务调度 | 不适合 | 进程应为它保持运行 |
| HTTP server | 不适用 | server 自身是活跃句柄 |
判断标准是:这个定时器是不是进程继续存在的理由。
如果不是,就可以考虑 unref()。
六、排查进程不退出时要看活跃句柄
很多测试脚本卡住不退出,原因可能是未清理定时器、未关闭 server、数据库连接没断。
unref() 可以处理非关键定时器,但核心资源仍要显式关闭。
记忆钩子:clear 是取消闹钟,unref 是告诉 Node 别为了这个闹钟不睡觉。
面试里把这个比喻讲出来,再补事件循环活跃句柄,就很清楚。 不要把它和定时器清理混淆。
七、常见误区与追问
- 误区:unref 会取消定时器。 它只是不让定时器单独保活进程,取消仍要用
clearTimeout或clearInterval。 - 误区:所有定时器都应该 unref。 核心调度任务需要保活进程,不能随便 unref。
- 误区:进程不退出一定是 timer 问题。 server、socket、数据库连接、文件监听也会让事件循环保持活跃。
- 追问:ref 有什么用? 它能恢复句柄保活能力,让定时器重新阻止进程退出。
- 追问:优雅停机超时为什么常用 unref? 兜底 timer 不应成为唯一阻止进程退出的原因。
- 追问:unref 后定时器还会执行吗? 如果进程因为其他活跃句柄仍在运行,到时间仍会执行。
八、加强记忆
ref/unref 题抓“进程保活”。定时器默认保活,unref 不取消任务,只是不让它单独拽住事件循环。CLI、兜底超时、非关键后台任务常用它;核心调度和服务资源要谨慎。