Node.js 事件循环机制与微任务执行顺序详解
本文深入解析Node.js事件循环机制,阐明V8引擎与libuv库的协作原理。重点对比process.nextTick、Promise微任务、定时器及IO回调的执行优先级,通过多个实验案例验证同步代码优先、nextTick先于Promise以及各阶段微任务检查规则,帮助开发者掌握异步编程的执行时序。
Node.js运行环境
JavaScript语言本身是同步、阻塞且单线程的,但Node.js并非如此。Node.js主要由三个核心组件构成:
- 外部依赖,例如V8引擎、libuv以及crypto模块。
- 提供文件和网络服务功能的C++模块。
- 基于上述C++模块封装而成的JavaScript库。

Node.js的异步特性主要依赖于libuv实现。libuv是一个跨平台的C语言库,主要负责提供对异步操作的支持。
Node运行时代码执行流程

当在Node.js中执行JavaScript代码时,由V8引擎负责处理代码执行。V8包含一块内存区域(堆)和调用栈。定义函数或变量时,从堆中分配内存;执行代码时将函数压入调用栈,函数返回时则弹出栈。
当执行异步操作时,libuv会接管该任务,并利用操作系统的异步机制来运行任务。如果操作系统缺乏相应的异步机制,则使用线程池来执行任务,从而保证主线程不被阻塞。
事件循环(Event Loop)

事件循环是Node.js应用启动后持续运行的一个无限循环。它包含六个不同的队列,每个队列存储不同类型的回调函数。
- 定时器队列(Timer queue):最小堆结构,由setTimeout和setInterval创建的任务进入此队列。
- IO队列:处理文件和网络操作的回调。
- Check队列:由setImmediate产生的任务进入此队列,这是Node.js特有的机制。
- Close队列:与异步任务的close事件相关的回调。
- nextTick队列:处理process.nextTick的回调。
- Promise队列:处理Promise微任务的回调。
除了两个微任务队列(nextTick和Promise)外,其他队列均由libuv提供。
工作原理
同步代码的执行优先级高于异步代码。事件循环仅在调用栈为空时开始运行。事件循环遵循以下优先级规则:
- 如果微任务队列中有任务,优先处理完所有微任务。其中,nextTick的执行优先级高于Promise。
- 定时器任务执行阶段。
- IO队列处理阶段。
- Check队列处理阶段。
- Close队列处理阶段。
需要注意的是,在定时器队列、IO队列、Check队列和Close队列执行完一个任务后,都会检查并运行微任务队列中的剩余任务。
实验验证
实验1
// index.js
console.log("console.log 1");
process.nextTick(() => console.log("this is process.nextTick 1"));
console.log("console.log 2");
输出结果
console.log 1 console.log 2 this is process.nextTick 1
结论:同步代码的执行顺序优先于异步代码。
实验2
// index.js
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
process.nextTick(() => console.log("this is process.nextTick 1"));
输出结果
this is process.nextTick 1 this is Promise.resolve 1
结论:nextTick的执行顺序先于Promise。
实验3
// index.js
process.nextTick(() => console.log("this is process.nextTick 1"));
process.nextTick(() => {
console.log("this is process.nextTick 2");
process.nextTick(() =>
console.log("this is the inner next tick inside next tick")
);
});
process.nextTick(() => console.log("this is process.nextTick 3"));
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
Promise.resolve().then(() => {
console.log("this is Promise.resolve 2");
process.nextTick(() =>
console.log("this is the inner next tick inside Promise then block")
);
});
Promise.resolve().then(() => console.log("this is Promise.resolve 3"));
实验3
// index.js
process.nextTick(() => console.log("this is process.nextTick 1"));
process.nextTick(() => {
console.log("this is process.nextTick 2");
process.nextTick(() =>
console.log("this is the inner next tick inside next tick")
);
});
process.nextTick(() => console.log("this is process.nextTick 3"));
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
Promise.resolve().then(() => {
console.log("this is Promise.resolve 2");
process.nextTick(() =>
console.log("this is the inner next tick inside Promise then block")
);
});
Promise.resolve().then(() => console.log("this is Promise.resolve 3"));
输出结果
this is process.nextTick 1 this is process.nextTick 2 this is process.nextTick 3 this is the inner next tick inside next tick this is Promise.resolve 1 this is Promise.resolve 2 this is Promise.resolve 3 this is the inner next tick inside Promise then block
解析:
在nextTick内部添加的nextTick任务仍然优先于Promise执行,因为只有在nextTick队列清空后,才会执行Promise队列中的任务。
在Promise内部添加的nextTick任务会晚于其他现有的Promise任务执行,因为此时正处于执行Promise的阶段,需要清空当前的Promise队列后,才会去检查nextTick队列。
实验4
// index.js
setTimeout(() => console.log("this is setTimeout 1"), 0);
setTimeout(() => {
console.log("this is setTimeout 2");
process.nextTick(() =>
console.log("this is inner nextTick inside setTimeout")
);
}, 0);
setTimeout(() => console.log("this is setTimeout 3"), 0);
process.nextTick(() => console.log("this is process.nextTick 1"));
process.nextTick(() => {
console.log("this is process.nextTick 2");
process.nextTick(() =>
console.log("this is the inner next tick inside next tick")
);
});
process.nextTick(() => console.log("this is process.nextTick 3"));
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
Promise.resolve().then(() => {
console.log("this is Promise.resolve 2");
process.nextTick(() =>
console.log("this is the inner next tick inside Promise then block")
);
});
Promise.resolve().then(() => console.log("this is Promise.resolve 3"));
输出结果
this is process.nextTick 1
this is process.nextTick 2
this is process.nextTick 3
his is the inner next tick inside next tick
this is Promise.resolve 1
this is Promise.resolve 2
this is Promise.resolve 3
this is the inner next tick inside Promise then block
this is setTimeout 1
this is setTimeout 2
this is inner nextTick inside setTimeout
this is setTimeout 3
结论:
nextTick的执行顺序先于Promise;微任务的优先级高于setTimeout;每个Timer任务执行后都会检查并执行微任务。
实验6
// index.js
setTimeout(() => console.log("this is setTimeout 1"), 1000);
setTimeout(() => console.log("this is setTimeout 2"), 500);
setTimeout(() => console.log("this is setTimeout 3"), 0);
输出结果
this is setTimeout 3 this is setTimeout 2 this is setTimeout 1
结论:Timer队列中的任务是按时间排序的。
实验7
// index.js
const fs = require("fs");
fs.readFile(__filename, () => {
console.log("this is readFile 1");
});
process.nextTick(() => console.log("this is process.nextTick 1"));
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
输出结果
this is process.nextTick 1 this is Promise.resolve 1
结论:微任务的执行顺序先于IO任务。
实验8
// index.js
const fs = require("fs");
setTimeout(() => console.log("this is setTimeout 1"), 0);
fs.readFile(__filename, () => {
console.log("this is readFile 1");
});
输出结果
不确定
解析:setTimeout 0通常内部会取1ms,即1ms后执行Timer任务。而CPU进入事件循环的时机是不确定的,因此有可能在进入事件循环时已经超过了1ms,此时会先执行Timer任务;也可能进入时定时任务尚未到期,则会先执行IO任务。
实验9
// index.js
const fs = require("fs");
fs.readFile(__filename, () => {
console.log("this is readFile 1");
});
process.nextTick(() => console.log("this is process.nextTick 1"));
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
setTimeout(() => console.log("this is setTimeout 1"), 0);
for (let i = 0; i < 2000000000; i++) {}
输出结果
this is process.nextTick 1 this is Promise.resolve 1 this is setTimeout 1 this is readFile 1
解析:
代码最后添加了循环,以确保进入事件循环时定时器任务已经到期,因此先执行Timer任务。
实验10
// index.js
const fs = require("fs");
fs.readFile(__filename, () => {
console.log("this is readFile 1");
});
process.nextTick(() => console.log("this is process.nextTick 1"));
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
setTimeout(() => console.log("this is setTimeout 1"), 0);
setImmediate(() => console.log("this is setImmediate 1"));
for (let i = 0; i < 2000000000; i++) {}
输出结果
this is process.nextTick 1 this is Promise.resolve 1 this is setTimeout 1 this is setImmediate 1 this is readFile 1
解析:按理说IO任务应该先于Check任务,但在第一次事件循环时,IO任务的回调函数并不在队列中。在两个队列之间会通过IO polling的方式去查看IO任务是否完成,只有当IO任务完成后,才会将回调函数添加到队列中,然后在下一轮循环时调用。
I/O events are polled and callback functions are added to the I/O queue only after the I/O is complete
www.builder.io/blog/visual…
以上就是Node.js事件循环运行代码解析的详细内容,更多关于Node.js事件循环的资料请关注本站其它相关文章!
macOS卸载Node.js指南:区分Homebrew与pkg安装包的处理方式
2026-11-05 11:28:12
JS逆向入门:浏览器Console调试与Node.js环境搭建指南
2026-11-05 10:57:15
Node.js 大版本升级需要注意哪些破坏性变更
2026-11-03 20:35:44
Node.js官网LTS版与Current版有什么区别
2026-11-03 19:33:49
Windows彻底卸载Node.js:清理AppData残留与PATH环境变量
2026-11-03 19:15:35
