详解NodeJS事件循环

官网:node官网-事件循环

浏览器中的事件循环是由HTML规范来定义,之后由各浏览器厂商实现的,而node中的事件循环的定义与实现均由libuv引擎完成。

node使用chrome v8引擎作为js解释器,v8引擎分析代码后,主线程立即执行同步任务,而异步任务则由libuv引擎驱动执行,而且不同异步任务的回调事件会放在不同的队列中等待主线程执行,不再是简单的宏任务队列和微任务队列。因此在nodeJS中,虽然程序运行表现出的整体状态与浏览器中传统的js大致相同,先同步后异步,但是对于异步的部分,node则依靠libuv引擎来进行更复杂的管理。

宏任务队列和微任务队列

六个基本阶段(六个宏任务队列)

在这里插入图片描述

  1. timers:计时器阶段,处理setTimeout()和setInterval()定时器的回调函数
  2. pending callbacks :待定回调阶段,用于处理系统级别的错误信息,例如 TCP 错误或者 DNS 解析异常
  3. idle,prepare:仅在内部使用,可以忽略不计
  4. poll:轮询阶段,等待I/O事件(如网络请求或者文件I/O等)的发生,然后执行对应的回调函数,并且会处理定时器相关的回调函数。如果没有任何I/O事件发生,此阶段可能会使事件循环阻塞
  5. check:检查阶段,处理 setImmediate() 的回调函数。check 的回调优先级比 setTimeout 高,比微任务要低
  6. close callbacks:关闭回调阶段,处理一些关闭的回调函数,比如 socket.on(‘close’)

nextTick队列(微任务队列)

该事件队列独立于6个阶段的事件队列之外,用于存储 process.nextTick() 的回调函数。

microTask队列(微任务队列)

该事件队列也独立于6个阶段的事件队列之外,用于存储 Promise(Promise.then()、Promise.catch()、Promise.finally())的回调函数。

NodeJS事件循环流程

以上六个基本阶段和两个独立的事件队列构成了node事件循环的核心部分,在一次循环迭代的流程中,需要注意:

  1. nextTick队列、microTask队列中的任务穿插于6个阶段之间进行,每个阶段进行前会先执行并清空nextTick队列、microTask队列中的回调任务(可以理解为一次循环迭代至少处理6次nextTick队列和microTask队列中的任务)
  2. nextTick队列、microTask队列执行的次数在Node v11.x版本前后有一些差异,(上文中的至少很有深意),具体如下:
    a. Node版本小于11时,nextTick队列、microTask队列中的任务只会在6个阶段之间进行,因此一次循环迭代最多处理6次这两个队列
    b. Node版本大于11时,任何一个阶段的事件队列中任务之间都会处理一次这两个队列,因此一次循环迭代至少处理6次这两个队列,上限则受各个阶段总任务数影响而不固定
    c. 上述2个版本之间的区别,被认为是一个应该要修复的bug,因此在v11.x之后,node修改了nextTick队列、microTask队列的处理时机。从宏、微任务的角度看,修复后的流程和传统js的事件循环保持了一致
  3. nextTick队列中任务的优先级高于microTask队列

setTimeout() 与 setlmmediate() 的特殊情况

我们知道 setTimeout()的回调是在 timers阶段执行,setImmediate()的回调是在 check阶段执行,并且事件循环是从 timers阶段开始的,那么 setTimeout()的回调一定会先于 setImmediate()的回调执行吗?答案是不一定。在只有这两个函数且近乎同时触发的情况下,它们回调的执行顺序不是固定的(受调用时机、计算机性能影响)。下面是一个例子:

// 示例1(node v12.16.3)
setTimeout(() => {console.log("setTimeout");
});setImmediate(() => {console.log("setImmediate");
});// 结果:
// setTimeout -> setImmediate
// 或
// setImmediate -> setTimeout

上面示例1中的这段代码输出结果就是不固定的,这是因为这种情况下回调不一定完全准备好了。因为主线程没有同步代码需要执行,程序一开始就进入了事件循环。这时setTimeout()的回调并不是一定完全准备好了,因此就可能会在第一次循环迭代的check阶段中执行setImmediate()的回调,再到第二次循环迭代的timers阶段执行setTimeout()的回调;同时也有可能setTimeout()的回调一开始就准备好了,这样就会按照先setTimeout()再setImmediate()的顺序执行回调。由此就造成了输出结果不固定的现象。

有以下两种方法可以使输出顺序固定:
① 人为添加同步代码的延时器,保证回调都准备好了(延时器的时长设定可能会受机器运行程序时的性能影响,因此该方法严格意义上并不能100%固定顺序)。
② 将这两个方法放入pending callbacks、idle,prepare、poll阶段中任意一个阶段即可,因为这些阶段执行完后是一定会先到check再到下一个迭代的timers。由于pending callbacks、idle,prepare阶段都偏向于系统内部,因此一般可以放入poll阶段中使用。

如下示例2,我们人为加上一个2000ms的延时器,输出的结果就固定了,如下所示:

//示例2(node v12.16.3)
setTimeout(() => {console.log("setTimeout");
});setImmediate(() => {console.log("setImmediate2");
});const sleep = (delay) => {const startTime = +new Date();while (+new Date() - startTime < delay) {continue;}
};
sleep(2000);// 结果:setTimeout -> setImmediate

如下示例3,我们将函数放入文件I/O的回调中,输出的结果也就固定了,如下所示:

//示例3(node v12.16.3)
const fs = require("fs");fs.readFile("./fstest.js", "utf8", (err, data) => {setTimeout(() => {console.log("setTimeout");});setImmediate(() => {console.log("setImmediate");});
});// 结果:setImmediate -> setTimeout 

NodeJS事件循环示例

console.log('1'); //1层同步//1层timers,setTimeout1
setTimeout(function() {console.log('2'); //2层同步process.nextTick(function() {console.log('3'); //2层nextTick队列})new Promise(function(resolve) {console.log('4'); //2层同步resolve();}).then(function() {console.log('5'); //2层microTask队列})
})process.nextTick(function() {console.log('6'); //1层nextTick队列
})new Promise(function(resolve) {console.log('7'); //1层同步resolve();
}).then(function() {console.log('8'); //1层microTask队列
})//1层timers,setTimeout2
setTimeout(function() {console.log('9'); //2层同步process.nextTick(function() {console.log('10'); //2层nextTick队列})new Promise(function(resolve) {console.log('11'); //2层同步resolve();}).then(function() {console.log('12'); //2层microTask队列})
})console.log('13'); //1层同步//(node v12.16.3)结果:1 -> 7 -> 13 -> 6 -> 8 -> 2 -> 4 -> 3 -> 5 -> 9 -> 11 -> 10 -> 12
//(node v8.16.0)结果:1 -> 7 -> 13 -> 6 -> 8 -> 2 -> 4 -> 9 -> 11 -> 3 -> 10 -> 5 -> 12

图解:node12+版本下的执行顺序

在这里插入图片描述

  1. 首先是1层的同步任务直接执行:1、7、13
  2. 进入事件循环
  3. 执行1层的nextTick队列:6
  4. 执行1层的microTask队列:8
  5. 进入timer阶段,由于setTimeout1的回调任务先进入队列,因此先执行setTimeout1的2层同步任务:2、4
  6. 执行setTimeout1的2层nextTick队列:3
  7. 执行setTimeout1的2层microTask队列:5
  8. setTimeout1的2层代码均执行完毕,再执行setTimeout2的2层同步代码:9、11
  9. 执行setTimeout2的2层nextTick队列:10
  10. 执行setTimeout2的2层microTask队列:12

和浏览器中事件循环的区别

浏览器事件循环在每次宏任务执行后,浏览器有机会进行UI渲染,但实际渲染取决于是否触发了重排或重绘。
● 执行环境:浏览器的事件循环主要运行在JavaScript引擎和渲染引擎之间,而Node.js的事件循环是运行在单独的线程中。这意味着在浏览器中,事件循环可能与渲染进程共享同一个线程,可能会出现线程阻塞的情况。而在Node.js中,事件循环运行在单独的线程中,不会导致浏览器那样的渲染阻塞
● 宏任务和微任务的实现方式:在浏览器中,宏任务和微任务是通过HTML5规范中定义的消息队列来实现的。所有异步任务都被分为宏任务和微任务两种类型,并依次加入到对应的队列中。当当前的宏任务执行完毕后,会立即执行所有的微任务,然后再选择下一个宏任务执行。常见的宏任务包括setTimeout、setInterval、DOM事件等,常见的微任务包括Promise.then、MutationObserver等
● 微任务队列的执行时机:在浏览器事件循环中,每执行完一个宏任务后,便要检查执行微任务队列。而在Node事件循环中,微任务是在两个阶段之间执行的,即在"上一阶段"执行完,"下一阶段"开始前执行微任务队列中的任务。这意味着Node中的微任务是在两个阶段之间执行的,而浏览器中的微任务是在每个宏任务执行完后执行的
● 事件循环的执行机制:浏览器的事件循环是在HTML5中定义的规范,而Node的事件循环则是由libuv库实现。这两个环境的事件循环执行机制不相同,不可以混为一谈
● process.nextTick()的优先级:在Node.js中,process.nextTick()的优先级要高于其他微任务,也就是说,在两个阶段之间执行微任务时,若存在process.nextTick(),则先执行它,然后再执行其他微任务
● 事件循环的执行顺序:浏览器的事件循环机制包括同步代码的执行、宏任务队列的执行、微任务队列的执行以及浏览器UI线程的渲染工作。如果有Web Worker任务,也会被执行。而在Node.js中,事件循环的执行顺序包括脚本作为宏任务的执行、微任务的执行以及可能的Web Worker任务的执行

应用场景影响

● Node.js 更强调后端服务的高效I/O处理和高并发能力,因此其事件循环机制侧重于快速响应I/O事件和维持稳定的事件处理流。
● 浏览器 则侧重于UI渲染和用户交互的实时响应,故其事件循环设计确保了UI的流畅更新和事件的及时处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/12657.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

立创EDA绘制PCB电路板

1、绘制好原理图后&#xff0c;点击设计---原理图转PCB&#xff0c;生成PCB文件 2、将元器件拖入电路板方框内&#xff0c;摆放布局并使用工具栏布线、放置过孔及丝印 3、然后顶层和底层铺铜 4、后面就可以生成制板文件发送嘉立创制板了。

Redis 本机无法访问

问题 我在服务器上有两个 Redis 实例&#xff0c;服务端口号分别是 6379 和 6380&#xff0c;Redis 服务器地址假设为 10.0.0.12。其中 6379 这个实例不需要密码即可访问&#xff0c;6380 需要密码访问。 在正常使用几天后&#xff0c;本机突然无法访问 6379 这个实例&#x…

springboot 定时任务解决方案

Scheduled (springboot 自带的 注解) 基于注解Scheduled默认为单线程&#xff0c;开启多个任务时&#xff0c;任务的执行时机会受上一个任务执行时间的影响。 EnableScheduling注解&#xff1a; 在配置类上使用&#xff0c;开启计划任务的支持&#xff08;类上&#xff09;。…

羊大师,羊奶真不错

羊大师&#xff0c;羊奶真不错 在众多乳制品中&#xff0c;羊奶以其独特的营养价值和美味口感赢得了人们的青睐。今天&#xff0c;小编羊大师想与大家分享羊奶的种种优点&#xff0c;让我们一同领略它的魅力。 羊奶的营养价值极高。它含有丰富的蛋白质、脂肪、矿物质和维生素&…

无独立显卡如何安装Pytorch

以前我是直接在colab中使用pytorch&#xff0c;非常方便&#xff0c;今天折腾了一上午&#xff0c;终于搞定了pytorh的安装和环境设置&#xff0c;分享下我的安装流程&#xff0c;遇到的问题和解决方案。 1. 用pip安装Pytorch 打开cmd窗口&#xff08;按win R&#xff09;&a…

端午佳节,品尝食家巷传统面点与黄米粽子礼盒

端午佳节&#xff0c;品尝食家巷传统面点与黄米粽子礼盒 在这个端午节来临之际&#xff0c;食家巷倾情推出一款别具特色的端午礼盒&#xff0c;将甘肃的传统面点与地方特色黄米粽子完美融合&#xff0c;为您带来一场美味与传统的邂逅。 这款礼盒以甘肃传统面点一窝丝、油饼和烤…

Android使用SQLite数据库no such table 问题

sqlite找不到表的问题&#xff0c;大多出现在模拟器里&#xff0c;因为db文件没找到&#xff0c;导致报错&#xff0c;为了避免此问题&#xff0c;增加了数据库文件是否存在的判断&#xff0c;就可以完美解决此类问题。如果能帮到您&#xff0c;麻烦点个赞。 可以直接看这个方…

电子邮箱怎么注册?电子邮箱注册教程,只需要三步

电子邮箱在我们工作和生活中都是必不可少的沟通工具。电子邮箱怎么注册&#xff1f;电子邮箱的注册步骤是怎么样的&#xff1f;本文将从电子邮箱注册前的准备资料介绍&#xff0c;到具体电子邮箱注册的三个步骤进行详细讲解。 一、电子邮箱注册前的准备 电子邮箱是一个具有唯…

概率密度函数pdf的某种解释与洞察

1.一个想法实验 我在想一个数,姑且称之为X,介于0和10之间(含0和10)。如果我不告诉你别的,你会想象X = 0的概率是多少?X = 4?假设我对任何特定的数字都没有偏好,你会想象十一个整数0,1,2,.….,10也是一样。因为所有的概率加起来必须是1,所以逻辑上的结论是给11个选项…

Python上下文管理器with块及@contextmanager的用法

上下文管理器和with块 上下文管理器对象存在的目的是为了管理with块,就像迭代器的存在是为了管理for循环一样 with 语句存在的意义是对一些常用的 try/finally 结构予以简化。这种结构能够保障一段代码在运行完成后实施某项操作,就算该段代码因为 return 语句、异常或者 sy…

联丰策略炒股官网分析地产链条中的家电,一个不能再忽视的板块

查查配“上涨放量,盘整缩量”是近期市场的一个重要特征,这说明空头衰竭、新的做多力量或正在蓄力。昨天我们也以调查问卷的方式与大家进行了讨论,对于市场未来将会如何演绎?近一半投票认为“牛在路上,逢低加仓”。与此同时,当前市场中,多条主线还在发力,比如地产链条中的家电,…

Python项目——基于回合制的RPG游戏设计与实现

基于回合制的RPG游戏设计与实现 项目概述 《魔法冒险》是一款基于回合制战斗的角色扮演游戏。玩家将创建一个角色&#xff0c;探索世界&#xff0c;战斗敌人&#xff0c;收集物品并提升等级。 项目设计报告 一、引言 本项目的目标是实现一个基于回合制战斗的 RPG 游戏&…

买了个彩票,哈哈哈哈哈。

买了个彩票-双色球&#xff0c;发现挺有意思的。 索性把双色球的所有期的中奖号码的数据都爬了下来&#xff0c;03至今&#xff0c;21年了。txt文本&#xff0c;6.5MB大小。 大家有啥好的建议&#xff0c;分析一下数据呢。

刘邦痛恨的叛徒雍齿,为何后来还被封了侯?

雍齿&#xff0c;原是沛县的世族出身&#xff0c;家庭往上追溯几代&#xff0c;也曾经显赫过。 虽然比不上先祖世代为楚将的项梁、项羽&#xff0c;但雍齿这个没落的世族后代&#xff0c;身上多多少少也还讲究点贵族遗风。 战国时期&#xff0c;以秦国的军功爵制为代表&#…

亚马逊等平台有哪些风控因素,如何真正做好自养号测评

很多测评人都知道亚马逊风控是非常严的&#xff0c;想要做好亚马逊测评需要解决很多风控问题&#xff0c;但是往往很多测评工作室技术不够&#xff0c;或者根本不了解风控点&#xff0c;以为只要IP或者指纹浏览器就可以做&#xff0c;这是非常错误的&#xff0c;也导致了很多隐…

SpringBoot(三)之打包方式

SpringBoot&#xff08;三&#xff09;之打包方式 Spring Boot 提供了几种常见的打包方式&#xff0c;具体取决于你的项目需求和偏好&#xff1a; JAR&#xff08;可执行的JAR文件&#xff09;&#xff1a; 这是 Spring Boot 的默认打包方式。它将项目的所有依赖打包到一个可…

3D 生成重建009-DreamGaussian使用gaussian splatting在两分钟内生成3d

3D 生成重建009-DreamGaussian使用gaussian splatting在两分钟内生成3d 文章目录 0 论文工作1 论文方法2 效果 0 论文工作 DreamGaussian是第一个使用gaussian splatting方法进行3d生成的工作。论文最先使用gaussian splatting替代原来用nerf表示3d。整体架构依然保留了原来的…

Jackson XML

Jackson XML 1 添加依赖2 XML转对象3 对象转XML4 根据路径读取 1 添加依赖 <dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId><version>2.11.3</version> </de…

在Spring的try-catch块中手动实现事务回滚

在Spring应用开发中&#xff0c;Transactional注解为我们提供了强大的声明式事务管理能力&#xff0c;使得我们能够专注于业务逻辑而无需过多关注底层的事务处理细节。然而&#xff0c;在某些特定场景下&#xff0c;开发者可能需要在捕获到特定异常时手动控制事务的回滚行为。本…