浏览器和nodejs中的eventloop
浏览器中的Event Loop
在浏览器中,设计成为了单线程。如果要处理异步请求,则需要增加一层调度逻辑,把js代码封装成一个个的任务,放在一个任务队列中,主线程不断的读取任务执行。每次调取任务,都会创建新的调用栈
- 宏任务:
setTimeout
,setInterval
,requestAnimationFrarme
,Ajax
,fetch
,script
- 微任务:
Promise.then
,MutationObserver
,Object.observe
等微任务执行才能执行宏任务
Node.js中的Eventloop
nodejs是一个新的js运行环境。同时要支持异步逻辑,定时器,io,网络请求
Nodejs任务宏任务之间是有优先级的,定时器的Timer的逻辑比Io的逻辑高,close的优先级就很低。
- 优先级:
Timers
,Pending
,poll
,check
,close
Timers Callback
:涉及到时间,越早执行越好Pending Callback
: 处理网络,io等异常的回调Poll Callback
: 处理io的data,网络的connectioncheck Callback
: 执行setImmediate的回调,特点是刚执行完io之后就能回调这个Close Callback
: 关闭资源的回调,晚点执行也不影响
Nodejs中的EventLoop每次是把当前优先级的所有宏任务跑完再去跑微任务,然后再去跑下一个优先级的宏任务
Node.js中的EventLoop的完整流程
- Timer阶段: 执行一定数量的定时器,太多的话留到下次执行
setTimeout
setInterval
- 微任务: 执行所有的nextTick的微任务。再去执行其他的普通微任务
- Pending阶段(I/Ocallback阶段): 执行一定数量的io和网络的异常回调,太多的话留到下次执行。处理一些上一轮循环中的少数未执行的io回调
- 微任务: 执行所有nextTick的微任务,在执行其他的普通微任务
- Idle/Prepare阶段: 内部的一个阶段
- 微任务: 执行所有的nextTick微任务,然后执行其他的普通微任务
- Poll阶段: 执行一定数量的文件的data回调,网络的connection回调,太多的放到下次执行,如果没有io回调并且没有timers,check阶段的回调处理,就阻塞在这里等待io时间
- 微任务: 执行一定数量的setImmediate的callback,太多的留到下次执行
- check阶段: 执行一定数量的setImmmediate的callback,太多的留到下次执行
- 微任务: 执行一定数量的nextTick的微任务,在执行其他的普通微任务
- close阶段: 执行一定数量的close事件的callback,太多的话留到下次执行
- 微任务: 执行所有的nextTick的微任务,在执行其他的普通微任务
如果执行到poll阶段,发现poll队列为空并且timers队列,check队列都没有执行,那么就阻塞在这里等待io事件
timer
timers阶段会执行setTimeout和setInterval回调,最初是由poll阶段控制的,在node中的定时器也不是准确的事件,只能是尽快执行poll
- 回到timer阶段执行回调
- 执行io回调
当设定了timer而且poll队列为空,会判断timer是否超时,如果有的话timer阶段执行回调如果poll队列不为空,会遍历回调队列并同步执行,直到队列为空或者达到系统限制 如果poll队列为空 如果由setImmediate回调要执行,poll阶段会停止,然后进入到check阶段执行回调 如果没有setImmediate回调需要执行,会等待回调并加入到队列中立即执行回调,需要由超时时间,防止一直等待下去
- check阶段
setImmediate()的回调会被加入到check队列中。
一开始执行栈的同步任务,执行完毕后,打印出start end,讲两个timer放入timer队列,执行微任务,打印出promise3console.log('start') setTimeout(() => { console.log('timer1') Promise.resolve().then(function() { console.log('promise1') }) }, 0) setTimeout(() => { console.log('time2') Promise.resolve().then(function() { console.log('promise2') }) }, 0) Promise.resolve().then(function() { console.log('promise3') }) console.log('end') // start => end => promise3 =>
进入timers阶段,执行timer1的回调函数,打印timer1,然后Promise.then放入微任务队列,然后执行timer2,打印timer2,将promise.then()放入微任务队列。timer阶段有几个setTimeout/setInterval都会依次执行,并不像浏览器端,没执行一个宏任务之后就在去执行一个微任务。 - 注意
setImmediate设计在poll阶段完成时执行,即check阶段
setTimeout设置在poll阶段为空闲的时候,而且达到设定时间之后才会执行。但是在timer阶段执行
在异步io内部调用的时候,总是先执行setImmediate,在执行setTimeout
process.nextTick,这个函数独立于event loop之外,有一个自己的队列,当每个阶段完成后,如果存在nextTick队列,就会清空队列中的所有回调函数,并且由于其他的微任务队列先执行
javascript最早就是用来写网页交互的逻辑,为了避免多线程同时修改dom的同步问题,所以被设计成为了单线程,解决了单线程的阻塞问题,加了一层调度逻辑,就是loop循环和task队列二,阻塞的线程放到其他的线程跑,支持了异步,为了支持高优先级的任务调度,引入了微任务队列,