Node.js 中的事件循环机制

一、是什么
在浏览器事件循环中,我们了解到javascript在浏览器中的事件循环机制,其是根据HTML5定义的规范来实现
而在NodeJS中,事件循环是基于libuv实现,libuv是一个多平台的专注于异步IO的库,如下图最右侧所示:

上图EVENT_QUEUE 给人看起来只有一个队列,但EventLoop存在6个阶段,每个阶段都有对应的一个先进先出的回调队列
二、流程
上节讲到事件循环分成了六个阶段,对应如下:

- timers阶段:这个阶段执行timer(setTimeout、setInterval)的回调
- 定时器检测阶段(timers):本阶段执行 timer 的回调,即 setTimeout、setInterval 里面的回调函数
- I/O事件回调阶段(I/O callbacks):执行延迟到下一个循环迭代的 I/O 回调,即上一轮循环中未被执行的一些I/O回调
- 闲置阶段(idle, prepare):仅系统内部使用
- 轮询阶段(poll):检索新的 I/O 事件;执行与 I/O 相关的回调(几乎所有情况下,除了关闭的回调函数,那些由计时器和 setImmediate() 调度的之外),其余情况 node 将在适当的时候在此阻塞
- 检查阶段(check):setImmediate() 回调函数在这里执行
- 关闭事件回调阶段(close callback):一些关闭的回调函数,如:socket.on('close', ...)
每个阶段对应一个队列,当事件循环进入某个阶段时, 将会在该阶段内执行回调,直到队列耗尽或者回调的最大数量已执行, 那么将进入下一个处理阶段
除了上述6个阶段,还存在process.nextTick,其不属于事件循环的任何一个阶段,它属于该阶段与下阶段之间的过渡, 即本阶段执行结束, 进入下一个阶段前, 所要执行的回调,类似插队
流程图如下所示:

在Node中,同样存在宏任务和微任务,与浏览器中的事件循环相似
微任务对应有:
- next tick queue:process.nextTick
- other queue:Promise的then回调、queueMicrotask
宏任务对应有:
- timer queue:setTimeout、setInterval
- poll queue:IO事件
- check queue:setImmediate
- close queue:close事件
其执行顺序为:
- next tick microtask queue
- other microtask queue
- timer queue
- poll queue
- check queue
- close queue
三、题目
通过上面的学习,下面开始看看题目
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')
分析过程:
先找到同步任务,输出script start
遇到第一个 setTimeout,将里面的回调函数放到 timer 队列中
遇到第二个 setTimeout,300ms后将里面的回调函数放到 timer 队列中
遇到第一个setImmediate,将里面的回调函数放到 check 队列中
遇到第一个 nextTick,将其里面的回调函数放到本轮同步任务执行完毕后执行
执行 async1函数,输出 async1 start
执行 async2 函数,输出 async2,async2 后面的输出 async1 end进入微任务,等待下一轮的事件循环
遇到第二个,将其里面的回调函数放到本轮同步任务执行完毕后执行
遇到 new Promise,执行里面的立即执行函数,输出 promise1、promise2
then里面的回调函数进入微任务队列
遇到同步任务,输出 script end
执行下一轮回到函数,先依次输出 nextTick 的函数,分别是 nextTick1、nextTick2
然后执行微任务队列,依次输出 async1 end、promise3
执行timer 队列,依次输出 setTimeout0
接着执行 check 队列,依次输出 setImmediate
300ms后,timer 队列存在任务,执行输出 setTimeout2
执行结果如下:
script start
async1 start
async2
promise1
promise2
script end
nextTick1
nextTick2
async1 end
promise3
setTimeout0
setImmediate
setTimeout2
最后有一道是关于setTimeout与setImmediate的输出顺序
setTimeout(() => {
console.log("setTimeout");
}, 0);
setImmediate(() => {
console.log("setImmediate");
});
输出情况如下:
情况一:
setTimeout
setImmediate
情况二:
setImmediate
setTimeout
分析下流程:
- 外层同步代码一次性全部执行完,遇到异步API就塞到对应的阶段
- 遇到
setTimeout,虽然设置的是0毫秒触发,但实际上会被强制改成1ms,时间到了然后塞入times阶段 - 遇到
setImmediate塞入check阶段 - 同步代码执行完毕,进入Event Loop
- 先进入
times阶段,检查当前时间过去了1毫秒没有,如果过了1毫秒,满足setTimeout条件,执行回调,如果没过1毫秒,跳过 - 跳过空的阶段,进入check阶段,执行
setImmediate回调
这里的关键在于这1ms,如果同步代码执行时间较长,进入Event Loop的时候1毫秒已经过了,setTimeout先执行,如果1毫秒还没到,就先执行了setImmediate
参考文献
- https://segmentfault.com/a/1190000012258592
Node.js 中的事件循环机制的更多相关文章
- node.js中的事件循环机制
http://www.cnblogs.com/dolphinX/p/3475090.html
- 【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick
[摘要] 官网博文翻译,nodejs中的定时器 示例代码托管在:http://www.github.com/dashnowords/blogs 原文地址:https://nodejs.org/en/d ...
- 【nodejs原理&源码赏析(7)】【译】Node.js中的事件循环,定时器和process.nextTick
目录 Event Loop 是什么? Event Loop 基本解释 事件循环阶段概览 事件循环细节 timers pending callbacks poll阶段 check close callb ...
- JavaScript中的事件循环机制跟函数柯里化
一.事件循环机制的理解 test();//按秒输出5个5 function test() { for (var i = 0; i < 5; i++) { setTimeout(() => ...
- 初步揭秘node.js中的事件
当你学习node.js的时候,Events是一个非常重要的需要理解的事情.非常多的Node对象触发事件,你能在文档API中找到很多例子.但是关于如何写自己的事件和监听,你可能还不太清楚.如果你不了解, ...
- node.js中的事件轮询Event Loop
任务队列/事件队列 "任务队列"是一个事件的队列,IO设备完成一项任务,就在"任务队列"中添加一个事件,表示相关的异步任务可以进入"执行栈" ...
- js中的事件缓存机制
异步任务指的是,不进入主线程.而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行. ...
- js高级-浏览器事件循环机制Event Loop
JavaScript 是队列的形式一个个执行的 同一时间只能执行一段代码,单线程的 (队列的数据结构) 浏览器是多线程的 JavaScript执行线程负责执行js代码 UI线程负责UI展示的 Jav ...
- Node.js中模块加载机制
1.模块查找规则-当模块拥有路径但没有后缀时:(require(‘./find’)) require方法根据模块路径查找模块,如果是完整路径,直接引入模块: 如果模块后缀省略,先找同名JS文件,再找同 ...
- node.js 中模块的循环调用问题详解
首先,我们看一下图示代码,每一个注释其实代表一个 js 文件.所以下面其实是三个 js 文件 .第一个是我们要运行的 main 文件,后面两个是 a, b 文件. 从上面可以看书 a ,b 两个模 ...
随机推荐
- XAF Blazor 中使用 Blazor 组件
前言 文章的标题是不是感觉有点奇怪,但实际我们在XAFBlazor中使用Blazor组件是很繁琐的,我们需要将Blazor组件封装成属性编辑器(PropertyEditor),再用非持久化对象(Non ...
- Windows 安装 Rust 并设置镜像加速
目录 下载rustup-init.exe(Rust安装工具) 使用镜像加速rustup安装 安装Rust 安装标准库源码 使用镜像加速cargo包下载 安装结果确认 更新.卸载和文档查看 参考文档 下 ...
- python文件获取并读取固定长度数据实例解析
一 概念 1 file 操作: 文件操作一般有open,write,read,close几种,这里重点是read固定长度数据. read() 用于从文件读取指定的字节数,如果未给定或为负则读取所有. ...
- Docker部署nginx配置SSL多目录
对自己第一次搭建nginx做个简要的笔记 第一步:创建宿主机挂载点目录 mkdir -p /home/nginx/{conf,conf.d,html,log,ssl} 第二步:安装简易版nginx,复 ...
- 缓存 SpringModules Cache ( spring 和 ehcache的整合 )
spring提供缓存bean方案 springbeancachecachingpath 下面格式整理有些混乱,spring3.1如何使用cache 缓存请参照:spring cache http:// ...
- hibernate之createQuery与createSQLQuery
信息: java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to com.miracle.dm.doc.catalog.m ...
- IDEA设置Maven华为镜像仓库
国内开发者由于网络原因,直接从中央仓下载第三包速度较慢或不稳定,使用国内镜像站可以很好解决该问题. 下面就介绍下如何将华为开源镜像站配置为maven的默认第三方库下载源. 1.打开系统用户目录&quo ...
- CentOS 安装后必需所做的初始化操作
CentOS 安装后必需所做的初始化操作 #关闭SELinux sed -i '/^SELINUX=/c SELINUX=disabled' /etc/selinux/config #关闭防火墙 sy ...
- 三维模型3DTile格式轻量化压缩处理效率提高的技术方浅析
三维模型3DTile格式轻量化压缩处理效率提高的技术方浅析 随着三维模型在各个领域的广泛应用,对于其格式的轻量化压缩处理和效率提高的需求也越发迫切.本文将介绍一些技术方法,帮助实现三维模型3DTile ...
- TP6框架--EasyAdmin学习笔记:数据表添加新参数,如何强制清除缓存
这是我写的学习EasyAdmin的第六章,这一章我给大家分享下如何在数据表中添加新参数,并强制清除缓存 这一章的主题是我在开发中碰到的一个问题,当我在网上疯狂查找解决方法依旧一无所获后,我又尝试了从底 ...