【二】Node.js事件循环中nextTick和Promise队列


欢迎来到系列文章中的第二篇,我们将继续以可视化方式探究 Node.js 事件循环。在第一篇文章中,我们了解到事件循环是 Node.js中至关重要的一部分,帮助协调同步和异步代码的执行。

它由六个不同的队列组成。一个 nextTick 队列和一个 promise 队列(在本系列文章中称为微任务队列),一个定时器队列,一个 I/O 队列,一个检查队列,最后一个是关闭队列。

在每个循环中,当适当时,回调函数会从队列中出列并在调用堆栈上执行。在开始这篇文章之前,让我们先了解如何在这些队列中排队回调函数。

回调函数排队

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

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

现在我们知道如何向这两个队列中添加回调函数了,让我们从第一个实验开始。

所有实验都使用CommonJS 模块格式进行。

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

这里有一段最小的代码片段,记录了三个不同的语句。第二个语句使用 process.nextTick() 方法将回调函数排队到 nextTick 队列中。

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

接下来,·process.nextTick()· 在调用堆栈上执行。这将回调函数排队到 nextTick 队列中,并被弹出。由于仍然有用户编写的代码要执行,回调函数必须等待执行。

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

来自nextTick队列的回调函数被推到堆栈上,console.log()也被推到堆栈上,执行并将相应的消息记录到控制台。

猜测
所有用户编写的同步 JavaScript代码优先于运行时希望最终执行的异步代码。

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

实验 2
// 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()

当调用堆栈执行第 1 行时,它将回调函数排入 promise 队列中。

当调用堆栈执行第 2 行时,它将回调函数排入 nextTick 队列中。

在第 2 行之后,没有更多的用户编写的代码要执行。

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

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

控制台显示 "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()语句的调用。每个回调函数都记录了相应的消息。

当调用堆栈执行第1行时,它将回调函数排入 Promise 队列。

当调用堆栈执行第2行时,它将回调函数排入 nextTick 队列。

在第2行之后没有更多的用户编写的代码需要执行。

控制权进入事件循环,在此处 nextTick 队列优先于 promise 队列(这是 Node.js 运行时的工作原理)。

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

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

猜测
所有 nextTick 队列中的回调函数在 promise 队列中的回调函数之前执行。

让我带您更详细地了解上述第二个实验的更复杂版本。

额外实验2
// 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()语句,每个都带有一个回调函数。

为了加快对此流程的解释,我将省略调用堆栈。当调用堆栈执行了所有六个语句时,nextTick 队列中有三个回调函数,而 promise 队列中有三个。在没有其他要执行的内容时,控制权进入事件循环。

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

接下来,执行第二个回调函数,该函数记录第二个日志语句。然而,此回调函数包含另一个 process.nextTick()的调用,该调用将内部日志语句排入 nextTick队列的末尾。

然后,Node 执行第三个 nextTick 回调,并将相应的消息记录到控制台。最初只有三个回调函数,但第二个回调函数添加了另一个回调函数到队列中,现在轮到了它。

事件循环推送内部 nextTick 回调,并执行 console.log()语句。

nextTick队列为空,控制流转到promise队列。promise 队列类似于 nextTick 队列。

首先记录 "Promise.resolve 1",然后是 "Promise.resolve 2"。但是,使用 process.nextTick()将一个函数添加到 nextTick 队列中。尽管如此,控制仍然留在promise 队列中,并继续执行其他回调函数。然后我们得到 Promise.resolve 3,此时 promise 队列为空。

Node将再次检查微任务队列中是否有新的回调。由于 nextTick队列中有一个回调,它会执行该回调,导致我们的最后一条日志语句。

这可能是一个稍微高级的实验,但推断仍然是相同的。

猜测
在所有回调函数执行之前,nextTick 队列中的所有回调函数都会执行。

在使用 process.nextTick()时要小心。过度使用此方法可能会导致事件循环饥饿,从而阻止队列的其余部分运行。即使有大量的 nextTick()调用,也可能会阻止I/O队列执行自己的回调。官方文档建议使用 process.nextTick()有两个主要原因:处理错误或在调用堆栈展开之后但在事件循环继续之前运行回调。在使用 process.nextTick()时,请务必谨慎使用。

额外实践

实验表明,所有用户编写的同步 JavaScript代码优先于运行时希望最终执行的异步代码,并且在 nextTick 队列中的所有回调函数之前,会执行所有promise 队列中的回调函数。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容