多图生动详解浏览器与Node环境下的Event Loop(下)

简介: 接上文。

三、Node.js中的JavaScript


注: 此次讨论的都是针对Node.js 11.x以上的版本


本文分别讨论了JS在浏览器环境和Node.js环境这两种情况,那自然是有所区别的,后者相对于前者的过程分得更加细致


(1)node中的Event Loop


我们来看一张Node.js的 Event Loop 简图


915d1c6018b06a5d8ade8912a842357c.png


Node.js的Event Loop 是基于libuv实现的


通过 Node.js 的官方文档可以得知,其事件循环的顺序分为以下六个阶段,每个阶段都会处理专门的任务:


  • timers: 计时器阶段,用于处理setTimeout以及setInterval的回调函数


  • pending callbacks: 用于执行某些系统操作的回调,例如TCP错误


  • idle, prepare: Node内部使用,不用做过多的了解


  • poll: 轮询阶段,执行队列中的 I/O 队列,并检查定时器是否到时


  • check: 执行setImmediate的回调


  • close callbacks: 处理关闭的回调,例如 socket.destroy()


以上六个阶段,我们需要重点关注的只有四个,分别是 timerspollcheckclose callbacks


这四个阶段都有各自的宏队列,只有当本阶段的宏队列中的任务处理完以后,才会进入下一个阶段。在执行的过程中会不断检测微队列中是否存在待执行任务,若存在,则执行微队列中的任务,等到微队列为空了,再执行宏队列中的任务(这一点与浏览器非常类似,但在Node 11.x版本之前,并不是这样的运行机制,而是运行完当前阶段队列中的所有宏任务以后才会去检测微队列。对于11.x 之后的版本,虽然在官网我还没找到相关文字说明是这样的,但通过无数次的运行,暂且可以说是这样的,若各位找到相关的说明,可以留下评论)


同理,Node.js也有宏任务和微任务之分,我们来看一下常用的都有哪些


名称 举例(常用)
宏任务 setTimeout 、setInterval 、setImmediate
微任务 Promise 、process.nextTick


可以看到,在Node.js对比浏览器多了两个任务,分别是宏任务 setImmediate 和 微任务 process.nextTick


setImmediate 会在 check 阶段被处理


process.nextTick 是Node.js中一个特殊的微任务,因此会为它单独提供一个队列,称为 next tick queue,并且其优先级大于其它的微任务,即若同时存在 process.nextTickpromise,则会先执行前者


总结一下,Node.js在事件循环中涉及到了4个宏队列和2个微队列,如图所示


ec7a0cbae3f671f66a5f3845ef04b99e.png


在了解了基本过程以后,我们先来写一道简单的题


setTimeout(() => {
    console.log(1);
}, 0)
setImmediate(() => {
    console.log(2);
})
new Promise(resolve => {
    console.log(3);
    resolve()
    console.log(4);
})
.then(() => {
    console.log(5);
})
console.log(6);
process.nextTick(() => {
    console.log(7);
})
console.log(8);
/* 打印结果:
   3
   4
   6
   8
   7
   5
   1
   2
*/


首先毫无疑问,同步的代码一定是最先打印的,因此先打印的分别是 3 4 6 8


再来判断一下异步的代码,setTimeout 被送入 timers queuesetImmediate 被送入 check queuethen() 被送入 other microtask queueprocess.nextTick 被送入 next tick queue


然后我们按照上面图中的流程,首先检测到微队列中有待执行任务,并且我们说过,next tick queue 的优先级高于 other microtask queue,因此先打印了 7,然后打印了 5 ;到此为止微队列中的任务都被执行完了,接着就进入 timers queue 中阶段,所以打印了 1,当前阶段的队列为空了,按照顺序进入 poll 阶段,但发现队列为空,所以进入了 check 阶段,上面说过了这个阶段是专门处理 setImmediate 的,因此最后就打印了 2


(2)setTimeout和setImmediate


不知刚才讲了那么多,大家有没有发现,一个循环中,timers 阶段是先于 check 阶段的,那么是不是就意味着 setTimeout 就一定比 setImmediate 先执行呢?我们来看个例子


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


我们用node运行该段代码多次,发现得到了如下两种结果:


// 第一种结果
setTimeout
setImmediate
// 第二种结果
setImmediate
setTimeout


这是为什么呢?


这里我们给 setTimeout 设置的延迟时间是 0,表面上看上去好像是没有延迟,但其实运行起来延迟时间是大于0的


然后node开启一个事件循环是需要一定时间的。假设node开启事件循环需要2毫秒,然后 setTimeout 实际运行的延迟时间是10毫秒,即事件循环开始得比 setTimeout 早,那么在第一轮事件循环运行到 timers 时,发现并没有 setTimeout 的回调需要执行,因此就进入了下一阶段,尽管此时 setTimeout 的延迟时间到了,但它只能在下一轮循环时被执行了,所以本次事件循环就先打印了 setImmediate,然后在下一次循环时打印了 setTimeout


这就是刚才第二种结果出现的原因


那么为何存在第一种情况也就更好理解了,那就是 setTimeout 的实际的延迟事件小于node事件循环的开启事件,所以能在第一轮循环中被执行


了解了为何出现上述原因以后,这里提出两个问题:


  1. 如何能做到一定先打印 setTimeout ,后打印 setImmediate


  1. 如何能做到一定先打印 setImmediate ,后打印 setTimeout


这里我们来分别实现一下这两个需求


实现一:


既然要让 setTimeout 先打印,那么就让它在第一轮循环时就被执行,那么我们只需要让事件循环开启的事件晚一点就好了。所以可以写一段同步的代码,让同步的代码执行事件长一点,然后就可以保证在进入 timers 阶段时,setTimeout 的回调已被送入 timers queue


setTimeout(() => {
    console.log('setTimeout');
}, 0)
setImmediate(() => {
    console.log('setImmediate');
})
let start = Date.now()
// 让同步的代码运行30毫秒
while(Date.now() - start < 30)


多次运行代码发现,每次都是先打印了 setTimeout,然后才打印的 setImmediate


实现二:


既然要让 setTimeout 后打印,那么就要想办法让它在第二轮循环时被执行,那么我们可以让 setTimeout 在第一轮事件循环跳过 timers 阶段后执行


刚开始我们讲过,poll 阶段是为了处理各种 I/O 事件的,例如文件的读取就属于 I/O 事件,所以我们可以把 setTimeoutsetImmediate 的代码放在一个文件读取操作的回调内,这样在第一轮循环到达 poll 阶段时,会将 setTimeout 送入 timers queue,但此时早已跳过了 timers 阶段,所以其只会在下一轮循环时被打印 ;同时 setImmediate 此时被送入了 check queue ,那么在离开 poll 阶段以后就可以顺利得先打印 setImmediate


const fs = require('fs');
fs.readFile(__filename, () => {
  setTimeout(() => {
    console.log('setTimeout');
  }, 0);
  setImmediate(() => {
    console.log('setImmediate');
  });
});


多次运行代码发现,每次都是先打印了 setImmediate,然后才打印的 setTimeout


四、结束语


一篇完整的Event Loop就讲到这里了,作者也是花了两天的时间,才将其搞懂,并且整理成博客,希望这篇文章对大家能有所帮助吧,哈哈最主要的是,在面试中不要像作者一样再在这个上面栽跟头了


相关文章
|
10月前
如何在不同的浏览器环境中确保时间戳转换的兼容性?
通过以上这些措施,可以在不同的浏览器环境中提高时间戳转换的兼容性,确保应用能够在各种浏览器中稳定运行,为用户提供良好的体验。
177 58
|
7月前
|
弹性计算 JavaScript 前端开发
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
Node.js 是一种高效的 JavaScript 运行环境,基于 Chrome V8 引擎,支持在服务器端运行 JavaScript 代码。本文介绍如何在阿里云上一键部署 Node.js 环境,无需繁琐配置,轻松上手。前提条件包括 ECS 实例运行中且操作系统为 CentOS、Ubuntu 等。功能特点为一键安装和稳定性好,支持常用 LTS 版本。安装步骤简单:登录阿里云控制台,选择扩展程序管理页面,安装 Node.js 扩展,选择实例和版本,等待创建完成并验证安装成功。通过阿里云的公共扩展,初学者和经验丰富的开发者都能快速进入开发状态,开启高效开发之旅。
|
4月前
|
人工智能 监控 JavaScript
HarmonyOS5云服务技术分享--ArkTS开发Node环境
本文详细讲解了在HarmonyOS(ArkTS API 9及以上)中使用云函数的开发技巧,结合Node.js和HTTP触发器,从零开始手把手教学。内容涵盖核心能力、开发流程(配置到部署)、高阶优化及常见问题解决,并提供实际应用场景示例。助你快速掌握Serverless开发,提升效率,探索跨端协作与AI集成等未来方向。
|
7月前
|
JavaScript 前端开发 数据可视化
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
426 2
【01】Cocos游戏开发引擎从0开发一款游戏-cocos环境搭建以及配置-Cocos Creator软件系统下载安装-node环境-优雅草卓伊凡
|
6月前
|
JavaScript Ubuntu Linux
如何在阿里云的linux上搭建Node.js编程环境?
本指南介绍如何在阿里云Linux服务器(Ubuntu/CentOS)上搭建Node.js环境,包含两种安装方式:包管理器快速安装和NVM多版本管理。同时覆盖全局npm工具配置、应用部署示例(如Express服务)、PM2持久化运行、阿里云安全组设置及外部访问验证等步骤,助你完成开发与生产环境的搭建。
|
7月前
|
弹性计算 JavaScript 前端开发
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
一键安装!阿里云新功能部署Nodejs环境到ECS竟然如此简单!
|
9月前
|
JavaScript
node环境之Error: Cannot find module ‘chalk’ 报错无法解决的问题—-网上说让你npm install chalk 基本是没有用的-优雅草央千澈解决方案
node环境之Error: Cannot find module ‘chalk’ 报错无法解决的问题—-网上说让你npm install chalk 基本是没有用的-优雅草央千澈解决方案
638 13
node环境之Error: Cannot find module ‘chalk’ 报错无法解决的问题—-网上说让你npm install chalk 基本是没有用的-优雅草央千澈解决方案
|
10月前
|
JavaScript 前端开发 数据处理
模板字符串和普通字符串在浏览器和 Node.js 中的性能表现是否一致?
综上所述,模板字符串和普通字符串在浏览器和 Node.js 中的性能表现既有相似之处,也有不同之处。在实际应用中,需要根据具体的场景和性能需求来选择使用哪种字符串处理方式,以达到最佳的性能和开发效率。
234 63
|
8月前
node环境之当我们遇到需要付费的依赖库@fortawesome/fontawesome-pro导致npm install无法进行怎么办-fontawesome-pro依赖库
node环境之当我们遇到需要付费的依赖库@fortawesome/fontawesome-pro导致npm install无法进行怎么办-fontawesome-pro依赖库
209 3
node环境之当我们遇到需要付费的依赖库@fortawesome/fontawesome-pro导致npm install无法进行怎么办-fontawesome-pro依赖库
|
9月前
|
机器学习/深度学习 JavaScript Cloud Native
Node.js作为一种快速、可扩展的服务器端运行时环境
Node.js作为一种快速、可扩展的服务器端运行时环境
173 8