网易首页 > 网易号 > 正文 申请入驻

Node.js 的事件循环机制

0
分享至

1.微任务

在谈论Node的事件循环机制之前,先补充说明一下 Node 中的“微任务”。这里说的微任务(microtasks)其实是一个统称,包含了两部分:

  • process.nextTick() 注册的回调 (nextTick task queue)
  • promise.then() 注册的回调 (promise task queue)

Node 在执行微任务时, 会优先执行 nextTick task queue 中的任务,执行完之后会接着执行 promise task queue 中的任务。所以如果 process.nextTick 的回调与 promise.then 的回调都处于主线程或事件循环中的同一阶段, process.nextTick 的回调要优先于 promise.then 的回调执行。

2.事件循环机制

如图,表示Node执行的整个过程。如果执行了任何非阻塞异步代码(创建计时器、读写文件等),则会进入事件循环。其中事件循环分为六个阶段:

由于Pending callbacks、Idle/Prepare 和 Close callbacks 阶段是 Node 内部使用的三个阶段,所以这里主要分析与开发者代码执行更为直接关联的Timers、Poll 和 Check 三个阶段。

Timers(计时器阶段):从图可见,初次进入事件循环,会从计时器阶段开始。此阶段会判断是否存在过期的计时器回调(包含 setTimeout 和 setInterval),如果存在则会执行所有过期的计时器回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Pending callbacks 阶段。

Pending callbacks:执行推迟到下一个循环迭代的I / O回调(系统调用相关的回调)。

Idle/Prepare:仅供内部使用。(详略)

Poll(轮询阶段)

当回调队列不为空时:

会执行回调,若回调中触发了相应的微任务,这里的微任务执行时机和其他地方有所不同,不会等到所有回调执行完毕后才执行,而是针对每一个回调执行完毕后,就执行相应微任务。执行完所有的回到后,变为下面的情况。

当回调队列为空时(没有回调或所有回调执行完毕):

但如果存在有计时器(setTimeout、setInterval和setImmediate)没有执行,会结束轮询阶段,进入 Check 阶段。否则会阻塞并等待任何正在执行的I/O操作完成,并马上执行相应的回调,直到所有回调执行完毕。

Check(查询阶段):会检查是否存在 setImmediate 相关的回调,如果存在则执行所有回调,执行完毕后,如果回调中触发了相应的微任务,会接着执行所有微任务,执行完微任务后再进入 Close callbacks 阶段。

Close callbacks:执行一些关闭回调,比如 socket.on('close', ...)等。

总结&注意:

  1. 1. 每一个阶段都会有一个FIFO回调队列,都会尽可能的执行完当前阶段中所有的回调或到达了系统相关限制,才会进入下一个阶段。

  2. 2. Poll 阶段执行的微任务的时机和 Timers 阶段 & Check 阶段的时机不一样,前者是在每一个回调执行就会执行相应微任务,而后者是会在所有回调执行完之后,才统一执行相应微任务。

3.setImmediate、setTimeout/setInterval 和 process.nextTick 执行时机对比

setImmediate:触发一个异步回调,在事件循环的 Check 阶段立即执行。

setTimeout:触发一个异步回调,当计时器过期后,在事件循环的 Timers 阶段执行,只执行一次(可用 clearTimeout 取消)。

setInterval:触发一个异步回调,每次计时器过期后,都会在事件循环的 Timers 阶段执行一次回调(可用 clearInterval 取消)。

process.nextTick:触发一个微任务(异步)回调,既可以在主线程(mainline)中执行,可以存在事件循序的某一个阶段中执行。

4.实例分析

第一组:

比较 setTimeout 与 setImmediate:

// test.js
setTimeout(() => {
console.log('setTimeout');
}, 0);

setImmediate(() => {
console.log('setImmediate');
});

结果:

分析:

从输出结果来看,输出是不确定的,既可能 "setTimeout" 在前,也可能 "setImmediate" 在前。从事件循环的流程来分析,事件循环开始,会先进入 Timers 阶段,虽然 setTimeout 设置的 delay 是 0,但其实是1,因为 Node 中的 setTimeout 的 delay 取值范围必须是在 [1, 2^31-1] 这个范围内,否则默认为1,因此受进程性能的约束,执行到 Timers 阶段时,可能计时器还没有过期,所以继续向下一个流程进行,所以会偶尔出现 "setImmediate" 输出在前的情况。如果适当地调大 setTimeout 的 delay,比如10,则基本上必然是 "setImmediate" 输出在前面。

第二组:

比较主线程(mainline)、Timers 阶段、Poll 阶段和 Check 阶段的回调执行以及对应的微任务执行的顺序:

// test.js
const fs = require('fs');

console.log('mainline: start')
process.nextTick(() => {
console.log('mainline: ', 'process.nextTick\n')
})

let counter = 0;
const interval = setInterval(() => {
console.log('timers: setInterval.start ', counter)
if(counter < 2) {
setTimeout(() => {
console.log('timers: setInterval.setTimeout')
process.nextTick(() => {
console.log('timers microtasks: ', 'setInterval.setTimeout.process.nextTick\n')
})
}, 0)

fs.readdir('./', (err, files) => {
console.log('poll: setInterval.readdir1')
process.nextTick(() => {
console.log('poll microtasks: ', 'setInterval.readdir1.process.nextTick')
process.nextTick(() => {
console.log('poll microtasks: ', 'setInterval.readdir1.process.nextTick.process.nextTick')
})
})
})

fs.readdir('./', (err, files) => {
console.log('poll: setInterval.readdir2')
process.nextTick(() => {
console.log('poll microtasks: ', 'setInterval.readdir2.process.nextTick')
process.nextTick(() => {
console.log('poll microtasks: ', 'setInterval.readdir2.process.nextTick.process.nextTick\n')
})
})
})

setImmediate(() => {
console.log('check: setInterval.setImmediate1')
process.nextTick(() => {
console.log('check microtasks: ', 'setInterval.setImmediate1.process.nextTick')
})
})

setImmediate(() => {
console.log('check: setInterval.setImmediate2')
process.nextTick(() => {
console.log('check microtasks: ', 'setInterval.setImmediate2.process.nextTick\n')
})
})
} else {
console.log('timers: setInterval.clearInterval')
clearInterval(interval)
}

console.log('timers: setInterval.end ', counter)
counter++;
}, 0);

console.log('mainline: end')

结果:

分析:

如图 mainline:可以看到,主线程中的 process.nextTick 是在同步代码执行完之后以及在事件循环之前执行,符合预期。

如图 第一次 timers:此时事件循环第一次到 Timers 阶段,setInterval 的 delay 时间到了,所以执行回调,由于没有触发直接相应的微任务,所以直接进入后面的阶段。

如图 第一次 poll:此时事件循环第一次到 Poll 阶段,由于之前 Timers 阶段执行的回调中,触发了两个非阻塞的I/O操作(readdir),在这一阶段时I/O操作执行完毕,直接执行了对应的两个回调。从输出可以看出,针对每一个回调执行完毕后,就执行相应微任务,微任务中再次触发微任务也会继续执行,并不会等到所有回调执行完后再去触发微任务,符合预期。执行完毕所有回调之后,因为还有调度了计时器,所以 Poll 阶段结束,进入 Check 阶段。

如图 第一次 check:此时事件循环第一次到 Check 阶段,直接触发对应的两个 setImmediate 执行。从输出可以看出,微任务是在所有的回调执行完毕之后才触发执行的,符合预期。执行完微任务后,进入后面阶段。

如图 第二次 timers:此时事件循环第二次到 Timers 阶段,首先输出了 "timers: setInterval.setTimeout" ,这是为什么?不要忘了,之前第一次执行 setInterval 的回调时,其实已经执行了一次其内部的 setTimeout(..., 0),但由于它并不能触发微任务,所以其回调没有被执行,而是进入到了后面的阶段,而是等到再次来到 Timers 阶段,根据FIFO,优先执行之前的 setTimeout 的回调,再执行 setInterval 的回调,而最后等所有回调执行完毕,再执行 setTimeout 的回调里面触发的微任务,最后输出的是 "timers microtasks: setInterval.setTimeout.process.nextTick",符合预期(所有回调执行完毕后,再执行相应微任务)。

后面的输出类似,所以不再做过多分析。


作者:qq575811412
链接:https://juejin.im/post/5e9a54c1e51d4546b50d52f0
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

/阅读下一篇/

你想知道的前后端协作规范都在这了

返回网易首页 下载网易新闻客户端
相关推荐
热点推荐
4年1.94亿!西决刚被淘汰!顶薪合同打没了

4年1.94亿!西决刚被淘汰!顶薪合同打没了

篮球教学论坛
2025-05-30 16:41:53
金砖国家对印度威胁退群集体淡定

金砖国家对印度威胁退群集体淡定

呼呼历史论
2025-05-30 14:40:29
广东一女子退货5万元手镯,交给快递员后被弄丢,商家:没收到货无法退款,快递:没有运费险 只能赔67.5元,当事人无奈:有运费险 只是做了退货动作

广东一女子退货5万元手镯,交给快递员后被弄丢,商家:没收到货无法退款,快递:没有运费险 只能赔67.5元,当事人无奈:有运费险 只是做了退货动作

东方网
2025-05-29 22:45:18
0.04秒绝杀!中国队终于夺冠,12年,等了整整12年,十大官媒盛赞

0.04秒绝杀!中国队终于夺冠,12年,等了整整12年,十大官媒盛赞

墨印斋
2025-05-29 14:46:17
布伦森32+5+5成头号功臣:5战165分+4场30+ 完胜哈利扳回一城

布伦森32+5+5成头号功臣:5战165分+4场30+ 完胜哈利扳回一城

醉卧浮生
2025-05-30 10:35:50
今日端午是“恶日”,今年端午牢记3个忌讳:1不晒、2不挂、3不吃

今日端午是“恶日”,今年端午牢记3个忌讳:1不晒、2不挂、3不吃

叮当当科技
2025-05-31 05:25:38
大理州发改委原副主任常树标主动投案,曾任县禁毒大队教导员,两月前刚被任命新职

大理州发改委原副主任常树标主动投案,曾任县禁毒大队教导员,两月前刚被任命新职

红星新闻
2025-05-30 16:15:12
百杆破百巨奖继续!准神和罗伯逊希望最大,赵心童墨菲或创造历史

百杆破百巨奖继续!准神和罗伯逊希望最大,赵心童墨菲或创造历史

世界体坛观察家
2025-05-30 11:56:22
小里弗斯带女友度假,场均20分,寻求3000万年薪,女友很漂亮

小里弗斯带女友度假,场均20分,寻求3000万年薪,女友很漂亮

大西体育
2025-05-30 14:33:10
向佐参加李晓华俱乐部夜宴,限量版茅台随便喝,一顿饭至少上十万

向佐参加李晓华俱乐部夜宴,限量版茅台随便喝,一顿饭至少上十万

央小北
2025-05-30 12:38:43
江苏一市委统战部副部长被查,曾任经开区管委会主任、市建委主任

江苏一市委统战部副部长被查,曾任经开区管委会主任、市建委主任

江海通报
2025-05-30 23:33:14
浙江婚礼上婆婆穿旗袍倒立练蛤蟆功儿媳尴尬到不行

浙江婚礼上婆婆穿旗袍倒立练蛤蟆功儿媳尴尬到不行

新时代的两性情感
2025-05-31 04:54:15
3天2亿3 核心,勒沃库森天崩重启,滕哈赫在德甲要沉沦?

3天2亿3 核心,勒沃库森天崩重启,滕哈赫在德甲要沉沦?

策略剖析
2025-05-30 12:37:03
女跑者真实经历分享:天热跑步谨慎走光,小心“春光乍泄”

女跑者真实经历分享:天热跑步谨慎走光,小心“春光乍泄”

马拉松跑步健身
2025-05-29 13:53:35
宣萱自曝仍与徐子珊保持联系,退圈4年杳无音讯,林峯也联系不上

宣萱自曝仍与徐子珊保持联系,退圈4年杳无音讯,林峯也联系不上

乡野小珥
2025-05-31 00:07:08
24岁年轻小伙娶44岁英语老师,当晚睡过头,次日男子掀开被子懵了

24岁年轻小伙娶44岁英语老师,当晚睡过头,次日男子掀开被子懵了

悬案解密档案
2025-05-28 10:42:42
家中的床不可空置?观音菩萨说:无人睡时,也要摆放这3样物品

家中的床不可空置?观音菩萨说:无人睡时,也要摆放这3样物品

风起青萍之未
2025-05-27 17:23:24
上海一快餐店19天核销了459张消费券,女老板被判刑!拿不出房租和工资,铤而走险→

上海一快餐店19天核销了459张消费券,女老板被判刑!拿不出房租和工资,铤而走险→

上观新闻
2025-05-30 13:28:25
戴笠与胡蝶的真实影像,胡蝶千娇百媚如仙子,戴笠面容冷峻显威严

戴笠与胡蝶的真实影像,胡蝶千娇百媚如仙子,戴笠面容冷峻显威严

尚曦读史
2025-05-29 08:25:13
赚300亿欠了5000亿,比亚迪真是下个“恒大”?两组数据一目了然

赚300亿欠了5000亿,比亚迪真是下个“恒大”?两组数据一目了然

北纬的咖啡豆
2025-05-30 10:50:02
2025-05-31 08:59:00
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 823关注度
往期回顾 全部

科技要闻

尊界S800上市 指导价70.8万起 8月中旬交车

头条要闻

日美将进行第四轮贸易谈判之际 石破茂对美强硬表态

体育要闻

唐斯的妈妈,一定会感到骄傲的

娱乐要闻

赵丽颖新剧扑街?演技扛剧能力遭质疑

财经要闻

向松祚:不必担忧美债高企 美可无限发债

汽车要闻

新增配色+动力升级 粤港澳车展探馆新款smart #1

态度原创

教育
亲子
旅游
艺术
公开课

教育要闻

中考数学求面积:送分题,很多学生都不要

亲子要闻

拒学门诊能否治愈成长之痛

旅游要闻

热闻|清明假期将至,热门目的地有哪些?

艺术要闻

故宫珍藏的墨迹《十七帖》,比拓本更精良,这才是地道的魏晋写法

公开课

李玫瑾:为什么性格比能力更重要?