定时器与 nextTick
setTimeout、setImmediate、process.nextTick。
30 分钟
定时器与 nextTick
nodejs-essentials 的 node-async 介绍了 Event Loop 概念。本课深入 定时器 API 与调度优先级:setTimeout、setInterval、setImmediate、process.nextTick——理解它们何时执行、为何顺序有时「反直觉」。
学习目标
- 区分宏任务(timers、I/O、check)与微任务(nextTick、Promise)
- 正确使用
setImmediate与setTimeout(fn, 0)的场景差异 - 避免
nextTick递归导致 Event Loop 饥饿
四类调度 API
| API | 阶段 | 典型用途 |
|---|---|---|
process.nextTick(fn) | 当前阶段结束后、下一轮 Loop 前 | 保证回调在 I/O 之前执行 |
Promise.then / queueMicrotask | 微任务队列 | 异步链、与 nextTick 交错 |
setTimeout / setInterval | timers 阶段 | 延迟、周期任务 |
setImmediate | check 阶段 | I/O 回调之后尽快执行 |
Node 文档:nextTick 优先级高于 Promise 微任务(与浏览器略有不同)。
执行顺序实验
console.log("1 sync");
setTimeout(() => console.log("2 setTimeout"), 0);
setImmediate(() => console.log("3 setImmediate"));
process.nextTick(() => console.log("4 nextTick"));
Promise.resolve().then(() => console.log("5 promise"));
console.log("6 sync");
主脚本里常见输出:1 → 6 → 4 → 5 → 2/3(2 与 3 在 I/O 上下文外顺序不保证)。在 fs.readFile 回调内 再设 timeout 与 immediate,则 immediate 通常先于 timeout——因为 check 阶段紧跟 poll。
setTimeout 的坑
- 最小延迟:浏览器与 Node 对
0ms都有下限(通常 1ms 量级),不能当作「立即」。 - drift:
setInterval不补偿执行耗时,长任务会导致间隔「挤在一起」;精确周期用setTimeout链或timers/promises的scheduler.wait(Node 实验 API)。 - unref:
timer.unref()让定时器不阻止进程退出——CLI 一次性脚本常用。
const t = setTimeout(() => {}, 60_000);
t.unref(); // 无其他 handle 时进程可正常结束