nodejs的事件循环
事件循环,即 Event Loop
,其实就是 JS 管理事件执行的一个流程,具体的管理方法由 JS 运行的环境决定,目前 JS 的主要运行环境有浏览器和 Node。
浏览器和 Node 的事件循环,都是先初始化一个循环,执行同步代码,遇到异步操做时,会将其交给对应的线程处理,主线程则继续往下执行,异步操做执行完毕后,对应的 callback 回调会被推入事件队列,并在合适的时机执行。每执行一次循环体的过程,咱们称之为一个 Tick。
与浏览器不一样的是,Node 的循环分为几个阶段,每一个阶段分别处理不一样的事件,而浏览器的循环不存在这样的阶段划分。下面咱们介绍一下 Node 事件循环的流程:
注意,setImmediate
是 Node 特有的宏任务,process.nextTick
是 Node 特有的微任务。
timers队列
执行 setTimeout
、setInterval
的回调。
poll轮询队列
执行大多数异步操做的回调,除了timers、checks,绝大部分回调都会放入该队列,比如:文件的读取、监听用户请求等。
如果poll中有回调,依次执行回调,直到清空队列。如果poll中没有回调,等待其他队列中出现回调,结束该阶段,进入下一阶段,如果其他队列也没有回调,持续等待,直到出现回调为止。但是有一种情况是,如果一直没有回调而卡在poll队列中的话,在系统实在是受不了的情况下,在poll中的事件会出去check,timer中按流程转一圈,在回到poll中继续等,当然这个等待的时间每个电脑都不一样,看硬件的。
check检查队列
执行 setImmediate
的回调。
nextTick和Promise
事件循环中,每次打算执行一个回调之前,必须要先清空nextTick和promise队列。
执行流程
计时器
在 node 中是按照顺序执行的,也就是说每一个代码都是从 timers 到 poll 到 check 的,都是执行完每个模块内部的每个事件,才到下一个模块。
setTimeout( function f1(){
console.log("6666");
} , 5000)
我们执行这样的代码首先在node中同步代码直接执行,异步代码进入事件循环,f1函数先经过timer到poll停下,因为除了计时器、setImmediate,绝大部分回调都会放入该队列,并停下一直等到自己或外部的回调触发才结束该阶段。也就是说f1要在poll中待至少5s才触发回调将console.log(“6666”)返回timer(因为timer是存放计时器的回调函数),这里要注意了,在这里不是直接输出6666,而是poll模块先检查自己有没有代码要执行,处理完后再到check模块,再到下一个循环的timer执行输出6666,然后一直进行这个循环直到整个事件循环没有东西要执行了。
服务器
很多人可能不理解poll模块为什么非要有一个内部或者外部的回调触发它,因为这里一般放置的都是需要一直开启的东西,比如服务器。
const http = require("http");
const server = http.createServer((req,res) => {
console.log("77777")
})
server.listen(3000)
这是一段开启服务器并且监听3000端口号的代码,这段代码一执行就会发生一直卡在poll模块里的情况,直到有用户访问3000端口才会是函数从poll来到check再到timer再到poll继续呆着,直到下一个访问请求。
nextTick和Promise
nextTick和Promise是什么?nextTick是超级vip,Promise是vip,在nodejs的事件循环中
大框代表事件队列,小框就代表事件回调,正常的流程是执行完timer中的3个事件后执行poll中的3个事件,但是在这之中每一个事件的执行完成之后都会看一下nextTick和Promise,如果nextTick中有东西就执行nextTick中的事件,nextTick执行完了之后就看一下Promise中有没有东西,有就执行Promise中的东西,于是执行流程就变成下面这样:
所以要注意:每一个事件的执行完成之后都会看一下nextTick和Promise。即使是nextTick和Promise内部的事件执行,也就是说如果在Promise中执行事件时,nextTick中添加了一个事件,即使Promise后面有事件在等待,也要回去执行nextTick新加的事件。在进入事件循环的时候第一时间看nextTick和Promise中有没有事件需要执行。
poll 阶段
poll 阶段主要作两件事情:
- 计算轮询时间
maxPollTime
- 执行 poll 队列中的回调
如下为 poll 阶段的流程:
- 事件循环进入 poll 阶段后,会先检查 poll 队列是否为空
- 若 poll 队列不为空,则遍历并同步执行队列里的回调(直到所有执行完或达到执行数的上限),若 poll 队列为空,则检查是否有待执行的
setImmediate
回调 - 若是有,则进入 check 阶段,若是没有,则原地等待新的回调被推入 poll 队列,并当即执行这些新推入的回调(等待时间的上限为前面计算出来的
maxPollTime
)
注意:poll 阶段空闲,即 poll 队列为空的时候,一旦有新的 setImmediate
回调,循环就会结束 poll 进入 check 阶段;或者一旦有新的 timer 计时结束,循环就会绕回 Timers 阶段;若是同时出现二者的回调,setImmediate
的优先级更高并发。