Event Loop事件循环简介
JavaScript 是单线程的,由于单线程会造成I/O阻塞,比如发送请求时未响应就可能造成页面停滞,为了解决这个问题,浏览器开始支持异步JS,异步JS就是把一些异步任务(ajax、定时器)等放到任务队列中,然后通过事件循环不断读取、触发任务队列中的异步代码,这种机制就叫做事件循环Event Loop。
Event Loop的核心代码是采用c++写的(属于NodeJs的范畴),本质上来说Event Loop就是采用轮询的方式不断读取、执行事件,今天我们要讨论的就是事件循环中的细节。
阶段
Event Loop内部分为以下阶段
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
上面的每一个阶段都有一个队列(先进先出),里面存放回调函数。每当Event Loop到达一个阶段,一般来说都会执行队列中的某些函数(也有可能不操作)
各阶段概览
- timers 阶段:这个阶段执行 setTimeout 和 setInterval 的回调函数。
- I/O callbacks 阶段:不在 timers 阶段、close callbacks 阶段和 check 阶段这三个阶段执行的回调,都由此阶段负责,这几乎包含了所有回调函数。
- idle, prepare 阶段(看起来是两个阶段,不过这不重要):event loop 内部使用的阶段(我们不用关心这个阶段)
- poll 阶段:获取新的 I/O 事件。在某些场景下 Node.js 会阻塞在这个阶段。
- check 阶段:执行 setImmediate() 的回调函数。
- close callbacks 阶段:执行关闭事件的回调函数,如 socket.on('close', fn) 里的 fn。
一个 Node.js 程序结束时,Node.js 会检查 event loop 是否在等待异步 I/O 操作结束,是否在等待计时器触发,如果没有,就会关掉 event loop。
timers
这个阶段很有可能是Event Loop开始的第一个阶段,主要存放setTimeout
或者setInterval
等宏任务
poll
这个阶段主要用来获取新的I/O事件,当 event loop 进入 poll 阶段,发现 poll 队列为空,event loop 检查了一下最近的计时器,大概还有 100 毫秒时间,于是 event loop 决定这段时间就停在 poll 阶段,当定时器任务快开始的时候,Event Loop会绕过poll阶段进入check阶段
check
这个阶段有一个API,面试的时候经常用的到,属于nodeJS的setImmediate
,它同样属于宏任务,但是相对于定时器来说,它的特点是要求更快执行
setImmediate和setTimeout
setImmediate 和 setTimeout 很相似,但是其回调函数的调用时机却不一样。
从所属的阶段队列来看,setImmediate属于check阶段,setTimeout属于timers阶段,那么两者之间到底谁先执行呢?
先看一段代码
setTimeout(()=>{console.log('timeout')},0)
setImmediate(()=>{console.log('immediate')},0)
一般来说,都会优先执行setTmmediate
,但是上面的代码实际执行顺序是这样的
为什么会有时先执行timeout,有时先执行immediate呢,这要从顺序看起,如果上面的代码setTimeout的时间设定为1000ms,那大家一定不会感到困惑,由于immediate存在于check阶段,当时间设定为1000ms时,Event Loop处于poll阶段,毕竟要等到1000ms才执行timers队列中的函数,所以Loop打算休息一下。
然后呢好像时间差不多了,Loop发现check阶段有个immediate函数,于是跑过去执行一下,执行完了就再跑到timers
阶段执行。
而上面产生困惑的最大原因是定时器设置时间为0,这就要看Event Loop开始时,所处的阶段。
如果Event Loop
此时在timers
阶段,队列中还没有定时器任务,又或者定时器任务还没到时间,那么必然会跳过此阶段,优先执行immediate
任务。
如果此时有任务,而且时间到了,那么必然会先执行setTimeout
,这也是上述代码产生困惑的原因。
下面我们对它进行改写
setTimeout(() => {
setTimeout(() => {
console.log("timeout");
}, 0);
setImmediate(() => {
console.log("immediate");
});
}, 1000);
上面的代码,1秒后,执行箭头函数,此时Event Loop并不在timers阶段,由于顺序是不可变的,所以总是会优先执行immediate
process.nextTick()
你可能发现 process.nextTick() 这个重要的异步 API 没有出现在任何一个阶段里,那是因为从技术上来讲 process.nextTick() 并不是 event loop 的一部分。实际上,不管 event loop 当前处于哪个阶段,nextTick 队列都是在当前阶段后就被执行了。
setTimeout(() => {
setTimeout(() => {
console.log("timeout");
}, 0);
setImmediate(() => {
console.log("immediate");
});
process.nextTick(()=>{
console.log('nexTick')
})
}, 1000);
上面的代码执行顺序是这样的
nextTick是在当前阶段马上执行,由于上面的代码执行后Loop处于poll阶段,所以会优先执行nextTick
为了更好得实验,我们再改一下代码
setTimeout(() => {
setTimeout(() => {
console.log("timeout");
process.nextTick(() => {
console.log("nexTick2");
});
}, 0);
setImmediate(() => {
console.log("immediate");
});
process.nextTick(() => {
console.log("nexTick");
});
}, 1000);
下面是结果,可以发现nextTick是在当前阶段马上执行的
nexTick
immediate
timeout
nexTick2
process.nextTick() 和 setImmediate()
这两个函数功能很像,而且名字也很令人疑惑。
process.nextTick() 的回调会在当前 event loop 阶段「立即」执行。 setImmediate() 的回调会在后续的 event loop 周期(tick)执行。
二者的名字应该互换才对。process.nextTick() 比 setImmediate() 更 immediate(立即)一些。
这是一个历史遗留问题,而且为了保证向后兼容性,也不太可能得到改善。所以就算这两个名字听起来让人很疑惑,也不会在未来有任何变化。
我们推荐开发者在任何情况下都使用 setImmediate(),因为它的兼容性更好,而且它更容易理解。
宏任务和微任务
异步任务中分宏任务和微任务,微任务总是比宏任务先执行
常见宏任务
常见微任务
经典面试题
setTimeout(()=> console.log(4))//宏任务
new Promise(resolve => {
resolve()//同步任务
console.log(1) //同步任务
}).then(()=> {
console.log(3) //微任务
})
console.log(2) //同步任务
改造成await
setTimeout(_ => console.log(4)) //宏任务
async function main() {
console.log(1) //同步任务
await Promise.resolve() //同步任务 相当于 resolve()
console.log(3) //相当于promise.then //微任务
}
main()
console.log(2) //同步任务
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!