Node.js 的事件循环是怎么工作的
How does the event loop work in Node.js
一句话:Node 是单线程执行 JS + 多线程做 I/O。 主线程跑 JS, 耗时的 I/O 交给 libuv 的线程池或操作系统, 完成后把回调放进队列, 事件循环再取出来执行。
六个阶段(顺序要记住):
- timers—— 到期的
setTimeout/setInterval - pending callbacks—— 上一轮延后的系统回调
- idle / prepare —— 内部用
- poll——取新的 I/O 事件, 大部分时间待在这里
- check——
setImmediate的回调 - close callbacks——
socket.on("close")这类
关键:每个阶段之间都会把微任务清空。而 Node 的微任务分两级:process.nextTick的优先级比 Promise 更高—— 它有自己的队列,在所有 Promise 微任务之前执行。
和浏览器的差别(这是考点):浏览器只有「宏任务 / 微任务」两档, 每次只取一个宏任务; Node 分了六个阶段, 同一阶段里的队列会一次取完。
另外 Node 多了setImmediate和 process.nextTick这两个浏览器没有的东西。
会追问:「setTimeout(fn, 0) 和setImmediate 谁先?」——不确定! 在主模块里两者顺序取决于进程启动耗时; 但在 I/O 回调里,setImmediate 一定更早(因为 check 阶段紧跟在 poll 后面, 而 timers 要等下一轮)。能答出「主模块里不确定」很加分。
「CPU 密集任务怎么办?」—— 单线程会被卡死。 用 worker_threads、 子进程,或者干脆交给别的服务。
In one line: Node runs your JavaScript on one thread and its I/O on many. The main thread runs JS; anything slow goes to libuv’s thread pool or straight to the OS, and when it finishes the callback is queued for the event loop to pick up.
Six phases, and the order matters:
- timers —
setTimeoutandsetIntervalcallbacks that are due - pending callbacks — system callbacks deferred from the previous turn
- idle / prepare — internal use
- poll — picks up new I/O events; this is where the process spends most of its time
- check —
setImmediatecallbacks - close callbacks — things like
socket.on("close")
The key point: microtasks are drained between every phase. And Node has two levels of them — process.nextTick outranks Promises. It gets its own queue and runs before any Promise microtask.
How this differs from the browser (this is the part they are testing): the browser has just two tiers, macrotask and microtask, and takes one macrotask per turn. Node has six phases, and within a phase it drains the whole queue.
Node also has setImmediate and process.nextTick, neither of which exists in a browser.
Follow-up: “Which fires first, setTimeout(fn, 0) or setImmediate?” — it is not guaranteed. At the top level of the main module the order depends on how long startup took. But inside an I/O callback setImmediate always wins, because check comes right after poll while timers has to wait for the next turn. Saying “undefined in the main module” is the answer that earns you points.
“What about CPU-bound work?” — one thread means it blocks everything. Use worker_threads, a child process, or hand the job to a different service entirely.