浏览器和Node不同的事件循环(Event Loop)
Posted 前端精读
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浏览器和Node不同的事件循环(Event Loop)相关的知识,希望对你有一定的参考价值。
前言
今日文章是由掘金 toBeTheLight 分享的一篇关于Event Loop的文章。在文章的最后,有对node版本不同,Event Loop不同表现的添加注释
正文从下面开始~~
背景
Event Loop也是js老生常谈的一个话题了。2月底看了阮一峰老师的《Node定时器详解》一文后,发现无法完全对标之前看过的js事件循环执行机制,又查阅了一些其他资料,记为笔记,感觉不妥,总结成文。
浏览器中与node中事件循环与执行机制不同,不可混为一谈。 浏览器的Event loop是在html5中定义的规范,而node中则由libuv库实现。同时阅读《深入浅出nodeJs》一书时发现比较当时node机制已有不同,所以本文node部分针对为此文发布时版本。强烈推荐读下参考链接中的前三篇。
浏览器环境
js执行为单线程(不考虑web worker),所有代码皆在主线程调用栈完成执行。当主线程任务清空后才会去轮询取任务队列中任务。
任务队列
异步任务分为task(宏任务,也可称为macroTask)和microtask(微任务)两类。 当满足执行条件时,task和microtask会被放入各自的队列中等待放入主线程执行,我们把这两个队列称为Task Queue(也叫Macrotask Queue)和Microtask Queue。
task:script中代码、setTimeout、setInterval、I/O、UI render。
microtask: promise、Object.observe、MutationObserver。
具体过程
执行完主执行线程中的任务。
取出Microtask Queue中任务执行直到清空。
取出Macrotask Queue中一个任务执行。
取出Microtask Queue中任务执行直到清空。
重复3和4。
即为同步完成,一个宏任务,所有微任务,一个宏任务,所有微任务......
注意
在浏览器页面中可以认为初始执行线程中没有代码,每一个script标签中的代码是一个独立的task,即会执行完前面的script中创建的microtask再执行后面的script中的同步代码。
如果microtask一直被添加,则会继续执行microtask,“卡死”macrotask。
部分版本浏览器有执行顺序与上述不符的情况,可能是不符合标准或js与html部分标准冲突。可阅读参考文章中第一篇。
new Promise((resolve, reject) =>{console.log(‘同步’);resolve()}).then(() => {console.log('异步')})
,即promise
的then
和catch
才是microtask,本身的内部代码不是。个别浏览器独有API未列出。
伪代码
while (true) {
宏任务队列.shift()
微任务队列全部任务()
}
node环境
js执行为单线程,所有代码皆在主线程调用栈完成执行。当主线程任务清空后才会去轮询取任务队列中任务。
循环阶段
在node中事件每一轮循环按照顺序分为6个阶段,来自libuv的实现:
timers:执行满足条件的setTimeout、setInterval回调。
I/O callbacks:是否有已完成的I/O操作的回调函数,来自上一轮的poll残留。
idle,prepare:可忽略
poll:等待还没完成的I/O事件,会因timers和超时时间等结束等待。
check:执行setImmediate的回调。
close callbacks:关闭所有的closing handles,一些onclose事件。
执行机制
几个队列
除上述循环阶段中的任务类型,我们还剩下浏览器和node共有的microtask和node独有的process.nextTick
,我们称之为Microtask Queue和NextTick Queue。
我们把循环中的几个阶段的执行队列也分别称为Timers Queue、I/O Queue、Check Queue、Close Queue。
循环之前
在进入第一次循环之前,会先进行如下操作:
同步任务
发出异步请求
规划定时器生效的时间
执行
process.nextTick()
开始循环
按照我们的循环的6个阶段依次执行,每次拿出当前阶段中的全部任务执行,清空NextTick Queue,清空Microtask Queue。再执行下一阶段,全部6个阶段执行完毕后,进入下轮循环。即:
清空当前循环内的Timers Queue,清空NextTick Queue,清空Microtask Queue。
清空当前循环内的I/O Queue,清空NextTick Queue,清空Microtask Queue。
清空当前循环内的Check Queu,清空NextTick Queue,清空Microtask Queue。
清空当前循环内的Close Queu,清空NextTick Queue,清空Microtask Queue。
进入下轮循环。
可以看出,nextTick
优先级比promise
等microtask高。setTimeout
和setInterval
优先级比setImmediate
高。
注意
如果在timers阶段执行时创建了
setImmediate
则会在此轮循环的check阶段执行,如果在timers阶段创建了setTimeout
,由于timers已取出完毕,则会进入下轮循环,check阶段创建timers任务同理。setTimeout
优先级比setImmediate
高,但是由于setTimeout(fn,0)
的真正延迟不可能完全为0秒,可能出现先创建的setTimeout(fn,0)
而比setImmediate
的回调后执行的情况。
伪代码
while (true) {
loop.forEach((阶段) => {
阶段全部任务()
nextTick全部任务()
microTask全部任务()
})
loop = loop.next
}
测试代码
function sleep(time) { let startTime = new Date() while (new Date() - startTime < time) {}
console.log('1s over')
}setTimeout(() => {
console.log('setTimeout - 1') setTimeout(() => {
console.log('setTimeout - 1 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 1 - then - then')
})
})
sleep(1000)
})setTimeout(() => {
console.log('setTimeout - 2') setTimeout(() => {
console.log('setTimeout - 2 - 1')
sleep(1000)
})
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then')
new Promise(resolve => resolve()).then(() => {
console.log('setTimeout - 2 - then - then')
})
})
sleep(1000)
})
浏览器输出:
setTimeout - 1 //1为单个task 1s over
setTimeout - 1 - then
setTimeout - 1 - then - then
setTimeout - 2 //2为单个task 1s over
setTimeout - 2 - then
setTimeout - 2 - then - then
setTimeout - 1 - 1 1s over
setTimeout - 2 - 1 1s overnode(v9)输出:
setTimeout - 1 1s over
setTimeout - 2 //1、2为单阶段task 1s over
setTimeout - 1 - then
setTimeout - 2 - then
setTimeout - 1 - then - then
setTimeout - 2 - then - then
setTimeout - 1 - 1 1s over
setTimeout - 2 - 1 1s over
node(其他)输出:
setTimeout - 1 1s over
setTimeout - 1 - then
setTimeout - 1 - then - then
setTimeout - 2 1s over
setTimeout - 2 - then
setTimeout - 2 - then - then
setTimeout - 1 - 1 1s over
setTimeout - 2 - 1 1s over
由此也可看出事件循环在浏览器和node的不同版本中的不同。
参考文章
Tasks, microtasks, queues and schedules 强烈推荐
不要混淆nodejs和浏览器中的event loop 强烈推荐
node中的Event模块 强烈推荐
理解事件循环一(浅析)
Node 定时器详解
以上是关于浏览器和Node不同的事件循环(Event Loop)的主要内容,如果未能解决你的问题,请参考以下文章
浏览器与Node的事件循环(Event Loop)有何区别?-浪里行舟