0

0

深入浅析Node事件循环中的微任务队列

青灯夜游

青灯夜游

发布时间:2023-04-13 17:39:15

|

2163人浏览过

|

来源于掘金社区

转载

image.png

之前的文章 中,我们了解到事件循环是 Node.js 的一个关键部分,用于协调同步和异步代码的执行。

它由六个不同的队列组成。一个 nextTick 队列和一个 Promise 队列(被称为微任务队列)、一个计时器队列、一个 I/O 队列、一个检查队列,最后是关闭队列。【相关教程推荐:nodejs视频教程编程教学

在每次循环中,回调函数会在适当时出队(dequeued)并在调用栈上执行。从本文开始,我们会进行一些实验以确保我们对事件循环的理解是正确的。

我们的第一组实验,将重点关注 nextTick 队列 和 Promise 队列。但在深入实验之前,需要先了解如何将回调函数排到队列中。

注意:所有实验都是使用 CommonJS 模块格式进行的。

入队回调函数

要将回调函数加入到 nextTick 队列中,我们可以使用内置的 process.nextTick() 方法。语法很简单:process.nextTick(callbackFn)。当该方法在调用栈上执行时,回调函数将被加入到 nextTick 队列中。

要将回调函数加入到 Promise 队列中,我们需要使用 Promise.resolve().then(callbackFn)。当 Promise 解决时(resolve),传递给 then() 的回调函数将被入队到 Promise 队列中。

现在我们已经了解了如何向两个队列添加回调函数,让我们开始第一个实验吧。

实验一

代码

// index.js
console.log("console.log 1");
process.nextTick(() => console.log("this is process.nextTick 1"));
console.log("console.log 2");

这段代码,记录了三个不同的语句。第二个语句使用 process.nextTick() 将回调函数排入 nextTick 队列。

可视化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed.gif

第一个 console.log() 语句被推入调用栈中执行。它在控制台中记录相应的消息,然后弹出堆栈。

接下来,在调用栈上执行 process.nextTick(),将回调函数入队到 nextTick 队列,并弹出。此时仍有用户编写的代码需要执行,因此回调函数必须等待其轮到。

执行继续进行,最后一个 console.log() 语句被推入堆栈中,该消息记录在控制台中,并将该函数从调用栈中弹出。现在,没有更多的用户编写同步代码需要执行,因此控制权进入事件循环。

nextTick 队列中的回调函数被推入调用栈,console.log() 被推入调用栈并且执行,在控制台记录相应的消息。

console.log 1
console.log 2
this is process.nextTick 1

推论

所有用户编写的同步 JavaScript 代码优先于异步代码执行。

让我们继续进行第二个实验。

实验二

代码

// index.js
Promise.resolve().then(() => console.log("this is Promise.resolve 1"));
process.nextTick(() => console.log("this is process.nextTick 1"));

我们有一个 Promise.resolve().then() 调用和一个 process.nextTick() 调用。

可视化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (1).gif

当调用栈执行第 1 行时,它将回调函数排队到 Promise 队列中;当调用栈执行第 2 行时,它将回调函数排队到 nextTick 队列中。

第 2 行后没有代码需要执行。

控制权进入事件循环,在其中 nextTick 队列优先于 Promise 队列(这是 Node.js 运行时的工作方式)。

事件循环执行 nextTick 队列回调函数,然后再执行 Promise 队列回调函数。

ARTi.PiCS
ARTi.PiCS

ARTi.PiCS是一款由AI驱动的虚拟头像生产器,可以生成200多个不同风格的酷炫虚拟头像

下载

控制台显示“this is process.nextTick 1”,然后是“this is Promise.resolve 1”。

this is process.nextTick 1
this is Promise.resolve 1

推论

nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。

让我带你走一遍上述第二个实验的更详细版本。

福利实验

代码

// 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"));

该代码包含三个 process.nextTick() 调用和三个 Promise.resolve() 调用语句。每个回调函数记录适当的消息。

但是,第二个 process.nextTick() 和第二个 Promise.resolve() 都有一个额外的 process.nextTick() 语句,每个都带有一个回调函数。

可视化

assets_YJIGb4i01jvw0SRdL5Bt_b32c606155c14ecfa60c8dc102f3bcf0_compressed (2).gif

为了加快对此可视化的解释,我将省略调用栈讲解。当调用栈执行所有 6 个语句时,nextTick 队列中有 3 个回调函数,Promise 队列中也有 3 个回调函数。没有其他要执行的内容后,控制权进入事件循环。

我们知道,nextTick 队列中具有高优先级。首先执行第 1 个回调函数,并将相应的消息记录到控制台中。

接下来,执行第 2 个回调函数,记录了第 2 条日志语句。但是,这个回调函数包含另一个对 process.nextTick() 的调用,该方法内的回调函数(译注:即() => console.log("this is the inner next tick inside next tick"))入队到nextTick 队列末尾。

然后 Node.js 执行第 3 个回调函数并将相应消息记录到控制台上。最初只有 3 个回调,但是因为第 2 次时向nextTick 队列又添加了一个,所以变成 4 个了。

事件循环将第 4 个回调函数推入调用栈,执行 console.log() 语句。

接着处理完 nextTick 队列之后,控制流程进入到 Promise 队列。Promise 队列与 nextTick 队列处理方式类似。

首先记录“Promise.resolve 1”,然后是“Promise.resolve 2”,这时因为调用 process.nextTick() 的原因,一个函数(译注:即 () => console.log("this is the inner next tick inside Promise then block") ) 被推入 nextTick 队列了。尽管如此,控制流程仍停留在 Promise 队列,因此还会继续执行队列中的其他函数。然后我们得到“Promise.resolve 3”,此时 Promise 队列为空。

Node.js 将再次检查微任务队列中是否有新的回调。由于 nextTick 队列中有一个回调,因此会执行这个回调,导致我们的最后一条日志输出。

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 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。

使用 process.nextTick() 时要小心。过度使用此方法可能会导致事件循环饥饿,从而阻止队列中的其余部分运行。当存在大量的 nextTick() 调用时,I/O 队列是无法执行自己的回调函数的。官方文档建议使用 process.nextTick() 的两个主要场景:处理错误或在调用栈为空事件循环继续之前执行回调用。所以在使用 process.nextTick() 时,要明智一些。

总结

实验表明,用户编写的所有同步 JavaScript 代码优先于异步代码执行,并且 nextTick 队列中的所有回调函数优先于 Promise 队列中的回调函数执行。

原文链接:Visualizing nextTick and Promise Queues in Node.js Event Loop,2023年3月30日,by Vishwas Gopinath

更多node相关知识,请访问:nodejs 教程

相关专题

更多
c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

78

2026.01.09

c++框架学习教程汇总
c++框架学习教程汇总

本专题整合了c++框架学习教程汇总,阅读专题下面的文章了解更多详细内容。

46

2026.01.09

学python好用的网站推荐
学python好用的网站推荐

本专题整合了python学习教程汇总,阅读专题下面的文章了解更多详细内容。

121

2026.01.09

学python网站汇总
学python网站汇总

本专题整合了学python网站汇总,阅读专题下面的文章了解更多详细内容。

11

2026.01.09

python学习网站
python学习网站

本专题整合了python学习相关推荐汇总,阅读专题下面的文章了解更多详细内容。

15

2026.01.09

俄罗斯手机浏览器地址汇总
俄罗斯手机浏览器地址汇总

汇总俄罗斯Yandex手机浏览器官方网址入口,涵盖国际版与俄语版,适配移动端访问,一键直达搜索、地图、新闻等核心服务。

71

2026.01.09

漫蛙稳定版地址大全
漫蛙稳定版地址大全

漫蛙稳定版地址大全汇总最新可用入口,包含漫蛙manwa漫画防走失官网链接,确保用户随时畅读海量正版漫画资源,建议收藏备用,避免因域名变动无法访问。

369

2026.01.09

php学习网站大全
php学习网站大全

精选多个优质PHP入门学习网站,涵盖教程、实战与文档,适合零基础到进阶开发者,助你高效掌握PHP编程。

44

2026.01.09

php网站搭建教程大全
php网站搭建教程大全

本合集专为零基础用户打造,涵盖PHP网站搭建全流程,从环境配置到实战开发,免费、易懂、系统化,助你快速入门建站!

12

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
React 教程
React 教程

共58课时 | 3.5万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 2.1万人学习

Bootstrap 5教程
Bootstrap 5教程

共46课时 | 2.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号