【Node】事件循环机制

news2024/12/24 3:20:37

Node 中的异步 API

  1. 定时器:setTimeoutsetInterval
  2. I/O 操作:文件读写、数据库操作、网络请求…
  3. Node 独有的 API:process.nextTicksetImmediate



事件循环的流程

  • Node 的事件循环分为 6 个阶段,这 6 个阶段会按顺序反复运行
  • 运行到某个阶段时,都会从该阶段对应的回调队列中取出函数执行
  • 当队列为空或者执行的回调函数数量达到系统设定的阈值,就会进入下一阶段

在这里插入图片描述

  1. timer 阶段:处理 setTimeoutsetInterval 的回调,由 poll 阶段控制
  2. I/O callbacks 阶段:处理系统级别的回调。eg: TCP 连接失败的回调…
  3. idle, prepare 阶段:仅 Node 内部使用,可以忽略
  4. poll 阶段:处理 I/O 操作的回调;
    事件循环空闲时,会在此阶段暂停,以等待新的回调加入
  5. check 阶段:执行 setImmediate 的回调
  6. close callbacks 阶段:执行关闭请求的回调。eg: socket.on('close', ...)

我们主要关注 timer poll check 阶段即可

demo1

console.log('脚本开始');

setTimeout(() => {
    console.log('定时器');
}, 10);

console.log('脚本结束');
  • 输出结果:'脚本开始' - '脚本结束' - '定时器'
  • 运行流程:
    1. 主线程从上往下执行代码,遇到异步代码则开启新线程执行,然后主线程继续往下执行。同步代码执行结束后,开始事件循环
    2. 执行到 timer 阶段:此时定时器还在执行中,所以 timer 队列中还没有回调,事件循环继续往下执行;执行到 poll 阶段:poll 队列也为空。此时事件循环会先查看 check 队列和 timer 队列是否为空,如果非空,则继续往下执行,如果为空,则会在 poll 阶段暂停事件循环,等待新的回调加入,再恢复执行
    3. 定时器执行完毕,其回调加入到 timer 队列中。事件循环继续执行,执行到 check 阶段,check 队列为空,事件循环继续往下执行;执行到 timer 阶段,timer 队列非空,执行 timer 队列的回调,输出 '定时器'

demo2

console.log('脚本开始');

setTimeout(() => {
    console.log('定时器');
}, 10);

// I/O 操作: 读取文件内容. 假设耗时 20ms
fs.readFile('demo.txt', (_, data) => {
    console.log(data);
});

console.log('脚本结束');
  • 输出结果:'脚本开始' - '脚本结束' - '定时器' - data
  • 运行流程:
    1. 主线程从上往下执行代码,遇到异步代码则开启新线程执行,然后主线程继续往下执行。同步代码执行结束后,开始事件循环
    2. 执行到 timer 阶段:此时定时器还在执行中,所以 timer 队列为空;执行到 poll 阶段:此时 I/O 操作也还在执行中,poll 队列也为空。事件循环在 poll 阶段暂停
    3. 10ms 后定时器执行完毕,回调加入 timer 队列,事件循环继续执行,输出 '定时器';事件循环执行到 poll 阶段并在此处暂停
    4. 20ms 后 I/O 操作执行完毕,回调加入 poll 队列,事件循环继续执行,输出 data

demo3

setImmediate:在效果上与 setTimeout 设置 0ms 类似;这里说的是类似,有两点需要注意一下:
① Node 中 setTimeout 的时间最小为 1ms;
setImmediate 的回调会被直接添加到 check 阶段的任务队列中

console.log('脚本开始');

setTimeout(() => {
    console.log('定时器');
}, 10);

fs.readFile('demo.txt', (_, data) => {
    console.log(data);
});

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

console.log('脚本结束');
  • 输出结果:'脚本开始' - '脚本结束' - 'setImmediate' - '定时器' - data
  • 运行流程:
    1. 主线程从上往下执行代码,遇到异步代码则开启新线程执行,然后主线程继续往下执行。同步代码执行结束后,开始事件循环
    2. 执行到 timer 阶段:此时定时器还在执行中,所以 timer 队列为空;执行到 poll 阶段:此时 I/O 操作也还在执行中,poll 队列也为空。事件循环查看 check 队列和 timer 队列是否为空,发现 check 队列中有 setImmediate 的回调,事件循环继续往下执行;执行到 check 阶段:取出 check 队列的回调执行,输出 'setImmediate';事件循环继续往下执行,并暂停在 poll 阶段
    3. 10ms 后定时器执行完毕,回调加入 timer 队列,事件循环继续执行,输出 '定时器';事件循环执行到 poll 阶段并在此处暂停
    4. 20ms 后 I/O 操作执行完毕,回调加入 poll 队列,事件循环继续执行,输出 data

demo4

setTimeout(() => {
    console.log('定时器');
}, 0);

setImmediate(() => {
    console.log('setImmediate');
});
  • 输出结果:'定时器' - 'setImmediate' / 'setImmediate' - '定时器' 都有可能
  • 注意:Node 中 setTimeout 的最小定时为 1ms
  • 情况 ①,事件循环开始后定时器才执行完:
    1. 执行到 timer 阶段:此时定时器还在执行中,所以 timer 队列为空;执行到 poll 阶段:poll 队列也为空。事件循环查看 check 队列和 timer 队列是否为空,发现 check 队列中有 setImmediate 的回调,事件循环继续往下执行;执行到 check 阶段:取出 check 队列的回调执行,输出 'setImmediate';事件循环继续往下执行,并暂停在 poll 阶段
    2. 1ms 后定时器执行完毕,回调加入 timer 队列,事件循环继续执行,输出 '定时器'
  • 情况 ②,定时器已经执行完了事件循环才开始:
    1. 执行到 timer 阶段:发现 timer 队列中有定时器的回调,执行并出输出 '定时器'。事件循环继续往下执行;执行到 poll 阶段:poll 队列为空。事件循环查看 check 队列和 timer 队列是否为空,发现 check 队列中有 setImmediate 的回调,事件循环继续往下执行;执行到 check 阶段:取出 check 队列的回调执行,输出 'setImmediate'

如果想确保两者的执行顺序,可以将这两个操作放到 I/O 操作的回调中:

fs.readFile('demo.txt', () => {
    setTimeout(() => {
        console.log('定时器');
    }, 0);

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

I/O 操作的回调在 poll 阶段被执行,setTiimdiate 的回调立即进入 check 队列,setTimeout 在计时结束后将回调加入 timer 队列。
此时,输出的顺序就一定是 'setImmediate' - '定时器'



微任务队列

  • 常见的宏任务:setTimeoutsetIntervalsetImmediatescript(整体代码)、I/O 操作…
    常见的微任务:process.nextTick()new Promise().then catch finally
  • 微任务比宏任务的优先级高,所以会在执行宏任务之前先清空微任务队列
  • 微任务中,nextTick 的优先级较高,会先执行
setTimeout(() => {
    console.log('timeout');
}, 0);

Promise.resolve().then(() => {
    console.log('promise');
});

process.nextTick(() => {
    console.log('nextTick');
});

输出结果:nextTick - promise - timeout

  • 每执行一个宏任务之前,都会先清空当前的微任务队列
  • 注意:旧版本的 Node (11 之前) 是在事件循环的各个阶段之间切换之前清空微任务队列
process.nextTick(() => {
    console.log('nextTick');
    Promise.resolve().then(() => {
        console.log('nextTick promise');
    });
});

Promise.resolve().then(() => {
    console.log('promise');
});

setTimeout(() => {
    console.log('timeout1');
    Promise.resolve().then(() => {
        console.log('timeout1 promise');
    });
}, 0);

setTimeout(() => {
    console.log('timeout2');
    Promise.resolve().then(() => {
        console.log('timeout2 promise');
    });
    process.nextTick(() => {
        console.log('timeout2 nextTick');
        Promise.resolve().then(() => {
            console.log('timeout2 nextTick promise');
        });
    });
}, 0);

setImmediate(() => {
    console.log('setImmediate');
    Promise.resolve().then(() => {
        console.log('setImmediate promise');
    });
});

输出结果:
nextTick -
promise - nextTick promise -
timeout1 - timeout1 promise -
timeout2 - timeout2 nextTick - timeout2 promise - timeout2 nextTick promise
timeout2 - timeout2 promise -
setImmediate - setImmediate promise

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

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

相关文章

高并发内存池项目

文章目录一、项目介绍二、什么是内存池2.1 池化技术2.2 内存池2.3 内存池的作用2.4 malloc三、设计定长内存池四、高并发内存池整体框架设计六、threadcache6.1 threadcache整体设计6.2 threadcache哈希桶映射对齐规则6.3 编写对齐和映射的相关函数6.4 编写ThreadCache类6.5 th…

电网头条知识竞赛题库答案(自动答题)

今天教你们自动完成2023年电网头条的知识竞赛,小编也为大家安排好了教程,首先呢需要知道电网助手,打开电网助手网页https://wwwl.lanzouw.com/b01w803yj 为了帮到大家,我特地分享出来,希望能给大家带来一丝丝便利&…

1.3第二周 星期二Samba、FTP

目录 01 Samba文件共享服务 Samba服务基础 2.主配置文件 02 linux文件传输服务 1.用户访问的Samba 03 FTP服务概述 1.vsftp知识预备 04 操作流程: 1.使用时记得装ftp的包:yum install ftp -y 2.装完之后启动服务&am…

【北京理工大学-Python 数据分析-2.1Matplotlib库入门】

Matplotlib库的使用 Matplotlib库由各种可视化类构成,内部结构复杂,受Matlab启发。matplotlib.pyplot是绘制各类可视化图形的命令字库,相当于快捷方式。 import matplotlib.pyplot as plt plt.plot([3,1,4,5,2]) plt.ylabel("Grade&qu…

实战四十七:基于机器(逻辑回归随机森林线性回归)学习预测销售门店的商品销量详细教程(代码+数据)

项目概述: 使用时间序列预测来预测来Corporacin Favorita 的数据的商店销售额。 具体来说,构建一个模型来更准确地预测在不同 Favorita 商店销售的数千种商品的单位销售额。您将使用包含日期、商店和商品信息、促销和单位销售的易于理解的训练数据集来练习您的机器学习技能。…

基于java ssm springboot+VUE疫情防疫系统系统前后端分离设计和实现

基于java ssm springbootVUE疫情防疫系统系统前后端分离设计和实现 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java毕设项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留…

Goland中使用GoPlantUml生成ER关系图

前言 Golang语言在近些年的开发语言中异军突起,在越来越多的公司项目中频繁出镜,也有越来越多的中间件选择使用Golang语言进行实现。正所谓源码之下无秘密,更友好地翻读源码对于理解功能特性以及后续使用非常有帮助,观摩学习源码也…

RMAN异地恢复-适用于数据库量比较大的场景

之前验证异地备份,只对数据库做个全备就备份恢复了,这种适用于数据库比较小的场景,因为如果数据库量大的话,备份,拷贝备份,恢复数据库的时间就比较长,停业务的时间就会比较长。 如果数据库比较…

JavaWeb知识汇总

文章目录前导一、数据库1.相关概念2.数据模型3.SQL4.约束5.数据库设计6.多表查询7.事务二、JDBC1.步骤2.JDBC事务管理3.数据库连接池三、Maven1.maven生命周期2.maven坐标详解3.依赖管理四、Mybatis1.快速入门2.Mapper代理开发3.核心配置文件4.参数传递5.注解开发五、HTML1.快速…

【进阶C语言】字符串函数+内存函数

文章目录一.字符串函数1.strlen功能:求字符串长度(不包括\0)函数模拟实现:2.1 strcmp功能函数模拟实现2.2 strncmp3.1 strcat功能函数模拟实现3.2strncat4.1 strcpy功能函数模拟实现4.2 strncpy5.strstr功能函数模拟实现6.strtok功…

论文《An Effective Consistency Constraint for Sequential Recommendation》

C2Rec: An Effective Consistency Constraint for SequentialRecommendation 这篇文章提出了序列推荐建模中一种有效的一致性约束防范,不用修改模型结构,仅仅添加2个额外的损失函数,就能达到非常好的效果。不像基于对比学习的方法&#xff0…

C++初阶--string

目录 string对象的创建: 遍历修改 const修饰的迭代器(只读): 反向迭代器: reserve与resize: find,rfind,substr: insert: erase: getchar、getline: string对…

Java基础学习笔记(十一)—— 包装类与泛型

包装类与泛型1 包装类1.1 基本类型包装类1.2 Integer类1.3 自动装箱 / 拆箱2 泛型2.1 泛型概述2.2 泛型的用法2.3 类型通配符1 包装类 1.1 基本类型包装类 基本类型包装类的作用 将基本数据类型封装成对象 的好处在于可以在对象中定义更多的功能方法操作该数据 public stat…

✿✿✿JavaScript --- Ajax异步请求与JSONP 跨域请求

目 录 一、原生的Ajax请求 1.异步和同步 2.Ajax介绍 3.实现方式 (1)原生的JS实现方式(了解) (2)原生AJax发送Post请求,并携带请求参数 二、JQuery封装后的Ajax 1.JQeury实现方式 2. $.get():发送get请求 3.$.post()&…

存储随笔2022年度最受欢迎文章榜单TOP15

回首2022感谢各位粉丝朋友的一路支持与陪伴存储随笔为您献上2022年度最受欢迎文章榜单TOP152023,一起向未来!TOP1:固态硬盘SSD入门级白皮书主要从固态硬盘的原理/接口形态/寿命/使用场景/等不同角度,来对比不同的人群需要什么样的…

[linux]vim编辑器

📟作者主页:慢热的陕西人 🌴专栏链接:Linux 📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言 本博客主要讲解vim的使用和一些vim的常用操作,以及如何解决…

Flow 转 LiveData 后数据丢了,肿么回事?

翻译自: https://arkadiuszchmura.com/posts/be-careful-when-converting-flow-to-livedata/ 前言 最近我在负责一段代码库,需要在使用 Flow 的 Data 层和仍然依赖 LiveData 暴露 State 数据的 UI 层之间实现桥接。好在 androidx.lifecycle 框架已经提供…

C语言-指针进阶-函数指针数组应用-计算器(9.2)

目录 1. 函数指针 2. 函数指针数组 2.1函数指针数组的定义 2.2函数指针数组应用 3. 指向函数指针数组的指针 思维导图&#xff1a; 1. 函数指针 直接上代码&#xff1a; #include <stdio.h>void test() {printf("hehe\n"); }int main() {printf("%…

【Java】数组的复制、反转、查找、排序

数组的复制、反转、查找、排序 复制 其中最关键的一点是搞清楚为什么数组复制和基本数据类型复制不同&#xff0c;是什么导致了这样的不同&#xff1f; 先来看例子 package com.atguigu.java;public class ArrayTest3 {public static void main(String[] args) {//新建arr数…

【Java数据结构与算法】Day2-高级排序(希尔、归并、快速、计数)

✅作者简介&#xff1a;热爱Java后端开发的一名学习者&#xff0c;大家可以跟我一起讨论各种问题喔。 &#x1f34e;个人主页&#xff1a;Hhzzy99 &#x1f34a;个人信条&#xff1a;坚持就是胜利&#xff01; &#x1f49e;当前专栏&#xff1a;【Java数据结构与算法】 &#…