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

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.

相关推荐
热点推荐
首个全场景AI发布会被极狐抢先了全新阿尔法S5上市补贴价9.98万起

首个全场景AI发布会被极狐抢先了全新阿尔法S5上市补贴价9.98万起

汽车公社
2026-03-18 21:07:43
首次集体盈利,新势力四强品牌路径分化

首次集体盈利,新势力四强品牌路径分化

百姓评车
2026-03-23 09:16:02
特朗普:同伊朗对话“完美”,已形成协议要点

特朗普:同伊朗对话“完美”,已形成协议要点

新京报
2026-03-23 23:24:08
10个让身体越来越好的秘诀,坚持做,肯定有效!

10个让身体越来越好的秘诀,坚持做,肯定有效!

运动健身号
2026-03-12 08:00:11
美以空袭致多处古迹受损,伊方已向联合国提出申诉,战火下的伊朗历史遗迹“令人痛心”

美以空袭致多处古迹受损,伊方已向联合国提出申诉,战火下的伊朗历史遗迹“令人痛心”

环球网资讯
2026-03-23 06:43:09
日本台湾油价接连下跌,大陆油价却疯涨至9元,差异原因一目了然

日本台湾油价接连下跌,大陆油价却疯涨至9元,差异原因一目了然

现代小青青慕慕
2026-03-24 00:51:02
钱再多有什么用,郭富城称无法接受年过六旬,每天喝几十杯咖啡

钱再多有什么用,郭富城称无法接受年过六旬,每天喝几十杯咖啡

阿斚田侃故事
2026-03-12 11:10:42
炸穿阿瓦士!美以联手端掉伊朗革命卫队总部,现场夷成白地

炸穿阿瓦士!美以联手端掉伊朗革命卫队总部,现场夷成白地

老马拉车莫少装
2026-03-23 13:00:44
铁了心打垮中国?最大威胁现身!这次不是美国,普京却笑到了最后

铁了心打垮中国?最大威胁现身!这次不是美国,普京却笑到了最后

阿七说史
2026-03-13 15:56:51
俄罗斯让中国心凉?真正可怕的不是西方围堵,而是我们低估了自己

俄罗斯让中国心凉?真正可怕的不是西方围堵,而是我们低估了自己

混沌录
2026-03-23 17:44:04
全新阿尔法S5补贴后9.98万起 增程纯电双动力、城区NOA入手即满配

全新阿尔法S5补贴后9.98万起 增程纯电双动力、城区NOA入手即满配

道哥说车
2026-03-18 18:29:55
权恩妃,有容乃大,真不是盖的!!这谁忍得住不爱?

权恩妃,有容乃大,真不是盖的!!这谁忍得住不爱?

小椰的奶奶
2026-03-23 19:56:47
连续击败日本队!中国组合爆冷夺金引关注,球迷直呼“未来可期”

连续击败日本队!中国组合爆冷夺金引关注,球迷直呼“未来可期”

罗掌柜体育
2026-03-23 11:17:35
全面断网!伊朗准备大干一场

全面断网!伊朗准备大干一场

十三级台阶
2026-03-21 11:07:58
河南周口一佳人太漂亮了,仙姿玉色,貌美如花,绝世佳人

河南周口一佳人太漂亮了,仙姿玉色,貌美如花,绝世佳人

天气观察站
2026-03-16 14:52:05
美为打通霍尔木兹海峡“不择手段” 伊朗或“鱼死网破”

美为打通霍尔木兹海峡“不择手段” 伊朗或“鱼死网破”

大象新闻
2026-03-22 19:53:04
赖清德称核二核三可能重启,蓝营青年:证明国民党核能主张有远见

赖清德称核二核三可能重启,蓝营青年:证明国民党核能主张有远见

海峡导报社
2026-03-22 18:37:02
98岁祖母离世!多年不来往的娘家来了十几个人,网友:娘死等舅来

98岁祖母离世!多年不来往的娘家来了十几个人,网友:娘死等舅来

火山詩话
2026-03-21 10:20:37
默克尔预测对了!俄乌冲突最大输家已经出现,不是俄罗斯不是美国

默克尔预测对了!俄乌冲突最大输家已经出现,不是俄罗斯不是美国

阿丰聊娱
2026-03-23 21:36:38
暴跌90%,理想天塌了!

暴跌90%,理想天塌了!

营销头版
2026-03-23 12:17:34
2026-03-24 03:40:49
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 823关注度
往期回顾 全部

科技要闻

裁掉2万多名员工后,扎克伯格对自己下手了

头条要闻

特朗普:伊朗还有最后一次机会

头条要闻

特朗普:伊朗还有最后一次机会

体育要闻

不敢放手一搏,你拿什么去争冠?

娱乐要闻

钟丽缇就女儿考拉争议道歉:女儿还小

财经要闻

市场见底了吗?谁在抛售?机构火线解读

汽车要闻

东风雪铁龙新凡尔赛C5X上市 官方一口价11.37万起

态度原创

房产
艺术
教育
时尚
军事航空

房产要闻

440亿!海南又一城城更计划曝光!TOP10房企巨头突然杀入!

艺术要闻

砸10亿!苏宁易购总部大楼,张近东雄心的象征

教育要闻

高三学生对教材中 “受精作用” 表述提意见,人教社回信

春天穿衣别太老气横秋,试试这些多巴胺穿搭,减龄养眼又舒适

军事要闻

伊朗回应美方威胁:将在战场上坚决对抗

无障碍浏览 进入关怀版