Node.js 事件循环机制与微任务执行顺序详解

作者:墨池时间:2026-10-26 12:55:29浏览:498

本文深入解析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运行环境

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

Node运行时代码执行流程

Node.js运行环境

当在Node.js中执行JavaScript代码时,由V8引擎负责处理代码执行。V8包含一块内存区域(堆)和调用栈。定义函数或变量时,从堆中分配内存;执行代码时将函数压入调用栈,函数返回时则弹出栈。

当执行异步操作时,libuv会接管该任务,并利用操作系统的异步机制来运行任务。如果操作系统缺乏相应的异步机制,则使用线程池来执行任务,从而保证主线程不被阻塞。

事件循环(Event Loop)

Node.js运行环境

事件循环是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事件循环的资料请关注本站其它相关文章!

作者最新文章