登录
注册
开源
企业版
高校版
搜索
帮助中心
使用条款
关于我们
开源
企业版
高校版
私有云
模力方舟
AI 队友
登录
注册
12月21日就在北京,来看大模型推理 + 国产硬件生态 + 开源社区分享,一起搞技术、聊开源、领福利~~
代码拉取完成,页面将自动刷新
捐赠
捐赠前请先登录
取消
前往登录
扫描微信二维码支付
取消
支付完成
支付提示
将跳转至支付宝完成支付
确定
取消
Watch
不关注
关注所有动态
仅关注版本发行动态
关注但不提醒动态
3
Star
45
Fork
21
DreamCoders
/
CoderGuide
代码
Issues
1169
Pull Requests
0
Wiki
统计
流水线
服务
JavaDoc
PHPDoc
质量分析
Jenkins for Gitee
腾讯云托管
腾讯云 Serverless
悬镜安全
阿里云 SAE
Codeblitz
SBOM
我知道了,不再自动展开
更新失败,请稍后重试!
移除标识
内容风险标识
本任务被
标识为内容中包含有代码安全 Bug 、隐私泄露等敏感信息,仓库外成员不可访问
说说对Nodejs中的事件循环机制理解?
待办的
#IAG9KX
陌生人
拥有者
创建于
2024-07-29 16:04
<h2>一、是什么</h2><p>在<a href="https://github.com/febobo/web-interview/issues/73" target="">浏览器事件循环</a>中,我们了解到<code>javascript</code>在浏览器中的事件循环机制,其是根据<code>HTML5</code>定义的规范来实现</p><p>而在<code>NodeJS</code>中,事件循环是基于<code>libuv</code>实现,<code>libuv</code>是一个多平台的专注于异步IO的库,如下图最右侧所示:</p><p><img src="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710534042708.png" alt="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710534042708.png" data-href="" style=""/></p><p>上图<code>EVENT_QUEUE</code> 给人看起来只有一个队列,但<code>EventLoop</code>存在6个阶段,每个阶段都有对应的一个先进先出的回调队列</p><h2>二、流程</h2><p>上节讲到事件循环分成了六个阶段,对应如下:</p><p><img src="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710534877720.png" alt="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710534877720.png" data-href="" style=""/></p><ul><li>timers阶段:这个阶段执行timer(setTimeout、setInterval)的回调</li><li>定时器检测阶段(timers):本阶段执行 timer 的回调,即 setTimeout、setInterval 里面的回调函数</li><li>I/O事件回调阶段(I/O callbacks):执行延迟到下一个循环迭代的 I/O 回调,即上一轮循环中未被执行的一些I/O回调</li><li>闲置阶段(idle, prepare):仅系统内部使用</li><li>轮询阶段(poll):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞</li><li>检查阶段(check):setImmediate() 回调函数在这里执行</li><li>关闭事件回调阶段(close callback):一些关闭的回调函数,如:socket.on('close', ...)</li></ul><p>每个阶段对应一个队列,当事件循环进入某个阶段时, 将会在该阶段内执行回调,直到队列耗尽或者回调的最大数量已执行, 那么将进入下一个处理阶段</p><p>除了上述6个阶段,还存在<code>process.nextTick</code>,其不属于事件循环的任何一个阶段,它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调,类似插队</p><p>流程图如下所示:</p><p><img src="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710535979466.png" alt="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710535979466.png" data-href="" style=""/></p><p>在<code>Node</code>中,同样存在宏任务和微任务,与浏览器中的事件循环相似</p><p>微任务对应有:</p><ul><li>next tick queue:process.nextTick</li><li>other queue:Promise的then回调、queueMicrotask</li></ul><p>宏任务对应有:</p><ul><li>timer queue:setTimeout、setInterval</li><li>poll queue:IO事件</li><li>check queue:setImmediate</li><li>close queue:close事件</li></ul><p>其执行顺序为:</p><ul><li>next tick microtask queue</li><li>other microtask queue</li><li>timer queue</li><li>poll queue</li><li>check queue</li><li>close queue</li></ul><h2>三、题目</h2><p>通过上面的学习,下面开始看看题目</p><pre><code class="language-js">async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('setTimeout0') }, 0) setTimeout(function () { console.log('setTimeout2') }, 300) setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick1')); async1(); process.nextTick(() => console.log('nextTick2')); new Promise(function (resolve) { console.log('promise1') resolve(); console.log('promise2') }).then(function () { console.log('promise3') }) console.log('script end')</code></pre><p>分析过程:</p><ul><li>先找到同步任务,输出script start</li><li>遇到第一个 setTimeout,将里面的回调函数放到 timer 队列中</li><li>遇到第二个 setTimeout,300ms后将里面的回调函数放到 timer 队列中</li><li>遇到第一个setImmediate,将里面的回调函数放到 check 队列中</li><li>遇到第一个 nextTick,将其里面的回调函数放到本轮同步任务执行完毕后执行</li><li>执行 async1函数,输出 async1 start</li><li>执行 async2 函数,输出 async2,async2 后面的输出 async1 end进入微任务,等待下一轮的事件循环</li><li>遇到第二个,将其里面的回调函数放到本轮同步任务执行完毕后执行</li><li>遇到 new Promise,执行里面的立即执行函数,输出 promise1、promise2</li><li>then里面的回调函数进入微任务队列</li><li>遇到同步任务,输出 script end</li><li>执行下一轮回到函数,先依次输出 nextTick 的函数,分别是 nextTick1、nextTick2</li><li>然后执行微任务队列,依次输出 async1 end、promise3</li><li>执行timer 队列,依次输出 setTimeout0</li><li>接着执行 check 队列,依次输出 setImmediate</li><li>300ms后,timer 队列存在任务,执行输出 setTimeout2</li></ul><p>执行结果如下:</p><pre><code >script start async1 start async2 promise1 promise2 script end nextTick1 nextTick2 async1 end promise3 setTimeout0 setImmediate setTimeout2</code></pre><p>最后有一道是关于<code>setTimeout</code>与<code>setImmediate</code>的输出顺序</p><pre><code class="language-js">setTimeout(() => { console.log("setTimeout"); }, 0); setImmediate(() => { console.log("setImmediate"); });</code></pre><p>输出情况如下:</p><pre><code class="language-js">情况一: setTimeout setImmediate 情况二: setImmediate setTimeout</code></pre><p>分析下流程:</p><ul><li>外层同步代码一次性全部执行完,遇到异步API就塞到对应的阶段</li><li>遇到<code>setTimeout</code>,虽然设置的是0毫秒触发,但实际上会被强制改成1ms,时间到了然后塞入<code>times</code>阶段</li><li>遇到<code>setImmediate</code>塞入<code>check</code>阶段</li><li>同步代码执行完毕,进入Event Loop</li><li>先进入<code>times</code>阶段,检查当前时间过去了1毫秒没有,如果过了1毫秒,满足<code>setTimeout</code>条件,执行回调,如果没过1毫秒,跳过</li><li>跳过空的阶段,进入check阶段,执行<code>setImmediate</code>回调</li></ul><p>这里的关键在于这1ms,如果同步代码执行时间较长,进入<code>Event Loop</code>的时候1毫秒已经过了,<code>setTimeout</code>先执行,如果1毫秒还没到,就先执行了<code>setImmediate</code></p>
<h2>一、是什么</h2><p>在<a href="https://github.com/febobo/web-interview/issues/73" target="">浏览器事件循环</a>中,我们了解到<code>javascript</code>在浏览器中的事件循环机制,其是根据<code>HTML5</code>定义的规范来实现</p><p>而在<code>NodeJS</code>中,事件循环是基于<code>libuv</code>实现,<code>libuv</code>是一个多平台的专注于异步IO的库,如下图最右侧所示:</p><p><img src="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710534042708.png" alt="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710534042708.png" data-href="" style=""/></p><p>上图<code>EVENT_QUEUE</code> 给人看起来只有一个队列,但<code>EventLoop</code>存在6个阶段,每个阶段都有对应的一个先进先出的回调队列</p><h2>二、流程</h2><p>上节讲到事件循环分成了六个阶段,对应如下:</p><p><img src="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710534877720.png" alt="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710534877720.png" data-href="" style=""/></p><ul><li>timers阶段:这个阶段执行timer(setTimeout、setInterval)的回调</li><li>定时器检测阶段(timers):本阶段执行 timer 的回调,即 setTimeout、setInterval 里面的回调函数</li><li>I/O事件回调阶段(I/O callbacks):执行延迟到下一个循环迭代的 I/O 回调,即上一轮循环中未被执行的一些I/O回调</li><li>闲置阶段(idle, prepare):仅系统内部使用</li><li>轮询阶段(poll):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞</li><li>检查阶段(check):setImmediate() 回调函数在这里执行</li><li>关闭事件回调阶段(close callback):一些关闭的回调函数,如:socket.on('close', ...)</li></ul><p>每个阶段对应一个队列,当事件循环进入某个阶段时, 将会在该阶段内执行回调,直到队列耗尽或者回调的最大数量已执行, 那么将进入下一个处理阶段</p><p>除了上述6个阶段,还存在<code>process.nextTick</code>,其不属于事件循环的任何一个阶段,它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调,类似插队</p><p>流程图如下所示:</p><p><img src="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710535979466.png" alt="https://jsd.onmicrosoft.cn/gh/iGaoWei/codercdn@master/question/20240627/2024062710535979466.png" data-href="" style=""/></p><p>在<code>Node</code>中,同样存在宏任务和微任务,与浏览器中的事件循环相似</p><p>微任务对应有:</p><ul><li>next tick queue:process.nextTick</li><li>other queue:Promise的then回调、queueMicrotask</li></ul><p>宏任务对应有:</p><ul><li>timer queue:setTimeout、setInterval</li><li>poll queue:IO事件</li><li>check queue:setImmediate</li><li>close queue:close事件</li></ul><p>其执行顺序为:</p><ul><li>next tick microtask queue</li><li>other microtask queue</li><li>timer queue</li><li>poll queue</li><li>check queue</li><li>close queue</li></ul><h2>三、题目</h2><p>通过上面的学习,下面开始看看题目</p><pre><code class="language-js">async function async1() { console.log('async1 start') await async2() console.log('async1 end') } async function async2() { console.log('async2') } console.log('script start') setTimeout(function () { console.log('setTimeout0') }, 0) setTimeout(function () { console.log('setTimeout2') }, 300) setImmediate(() => console.log('setImmediate')); process.nextTick(() => console.log('nextTick1')); async1(); process.nextTick(() => console.log('nextTick2')); new Promise(function (resolve) { console.log('promise1') resolve(); console.log('promise2') }).then(function () { console.log('promise3') }) console.log('script end')</code></pre><p>分析过程:</p><ul><li>先找到同步任务,输出script start</li><li>遇到第一个 setTimeout,将里面的回调函数放到 timer 队列中</li><li>遇到第二个 setTimeout,300ms后将里面的回调函数放到 timer 队列中</li><li>遇到第一个setImmediate,将里面的回调函数放到 check 队列中</li><li>遇到第一个 nextTick,将其里面的回调函数放到本轮同步任务执行完毕后执行</li><li>执行 async1函数,输出 async1 start</li><li>执行 async2 函数,输出 async2,async2 后面的输出 async1 end进入微任务,等待下一轮的事件循环</li><li>遇到第二个,将其里面的回调函数放到本轮同步任务执行完毕后执行</li><li>遇到 new Promise,执行里面的立即执行函数,输出 promise1、promise2</li><li>then里面的回调函数进入微任务队列</li><li>遇到同步任务,输出 script end</li><li>执行下一轮回到函数,先依次输出 nextTick 的函数,分别是 nextTick1、nextTick2</li><li>然后执行微任务队列,依次输出 async1 end、promise3</li><li>执行timer 队列,依次输出 setTimeout0</li><li>接着执行 check 队列,依次输出 setImmediate</li><li>300ms后,timer 队列存在任务,执行输出 setTimeout2</li></ul><p>执行结果如下:</p><pre><code >script start async1 start async2 promise1 promise2 script end nextTick1 nextTick2 async1 end promise3 setTimeout0 setImmediate setTimeout2</code></pre><p>最后有一道是关于<code>setTimeout</code>与<code>setImmediate</code>的输出顺序</p><pre><code class="language-js">setTimeout(() => { console.log("setTimeout"); }, 0); setImmediate(() => { console.log("setImmediate"); });</code></pre><p>输出情况如下:</p><pre><code class="language-js">情况一: setTimeout setImmediate 情况二: setImmediate setTimeout</code></pre><p>分析下流程:</p><ul><li>外层同步代码一次性全部执行完,遇到异步API就塞到对应的阶段</li><li>遇到<code>setTimeout</code>,虽然设置的是0毫秒触发,但实际上会被强制改成1ms,时间到了然后塞入<code>times</code>阶段</li><li>遇到<code>setImmediate</code>塞入<code>check</code>阶段</li><li>同步代码执行完毕,进入Event Loop</li><li>先进入<code>times</code>阶段,检查当前时间过去了1毫秒没有,如果过了1毫秒,满足<code>setTimeout</code>条件,执行回调,如果没过1毫秒,跳过</li><li>跳过空的阶段,进入check阶段,执行<code>setImmediate</code>回调</li></ul><p>这里的关键在于这1ms,如果同步代码执行时间较长,进入<code>Event Loop</code>的时候1毫秒已经过了,<code>setTimeout</code>先执行,如果1毫秒还没到,就先执行了<code>setImmediate</code></p>
评论 (
0
)
登录
后才可以发表评论
状态
待办的
待办的
进行中
已完成
已关闭
负责人
未设置
标签
Node
未设置
标签管理
里程碑
未关联里程碑
未关联里程碑
Pull Requests
未关联
未关联
关联的 Pull Requests 被合并后可能会关闭此 issue
分支
未关联
未关联
master
开始日期   -   截止日期
-
置顶选项
不置顶
置顶等级:高
置顶等级:中
置顶等级:低
优先级
不指定
严重
主要
次要
不重要
参与者(1)
1
https://gitee.com/DreamCoders/CoderGuide.git
git@gitee.com:DreamCoders/CoderGuide.git
DreamCoders
CoderGuide
CoderGuide
点此查找更多帮助
搜索帮助
Git 命令在线学习
如何在 Gitee 导入 GitHub 仓库
Git 仓库基础操作
企业版和社区版功能对比
SSH 公钥设置
如何处理代码冲突
仓库体积过大,如何减小?
如何找回被删除的仓库数据
Gitee 产品配额说明
GitHub仓库快速导入Gitee及同步更新
什么是 Release(发行版)
将 PHP 项目自动发布到 packagist.org
评论
仓库举报
回到顶部
登录提示
该操作需登录 Gitee 帐号,请先登录后再操作。
立即登录
没有帐号,去注册