Please enable Javascript to view the contents

Eventloop的理解

 ·  ☕ 3 分钟

javascript 是一门单线程语言,但是其中涉及到了需要很多异步的场景
比如所有的请求是异步的,读取资源是异步的,计时器也是异步的

那么这究竟是怎么实现的?

nodejs 的引擎是由 C++实现的,为了支持 js 运行时的异步,nodejs 引擎提供了 Eventloop 的事件循环机制

Eventloop 是指事件执行的阶段循环,Eventloop 在 nodejs 和浏览器里是不同


Eventloop 的阶段(简化)

官方文档(原文的翻译可以看这里)有 6 个阶段,
现在简化为 3 个阶段来看,阶段的执行顺序是 1、2、3、1、2、3…

  1. timers:执行 setTimeout 和 setInterval 的回调函数
  2. poll:等待 time 到达某个点后触发 timers 中的回调函数
  3. check:执行 setImmediate() 的回调函数

setTimeout(fn,0)和 setImmediate 谁先执行?

这里先说一下 nodejs 中开启 Eventloop 时发生的事:

  1. 开启事件循环 开启 nodejs 的 Eventloop 进程
  2. 同时异步得开始开始执行 js,开启 v8 引擎进程

1 和 2 因为是异步发生的,所以不知道谁会先结束,他们的顺序可能是 1 开启完毕然后 2 开启完毕束,也可能是 2 先开启完毕再 1 开启完毕

理想情况下 环境 1.Eventloop 先开启完毕
接着环境 2.v8 引擎再开启完毕,然后开始分析和执行 js 代码(此时 Eventloop 已经运行到阶段 2.poll 了)

在分析代码时:
setTimeout 和 setInterval 会放到阶段 1.timers 的执行队列里
setImmediate 会放到 阶段 3.check 的执行队列里

Eventlop 已经运行到 阶段 2.poll 中开始等待
如果在阶段 2.poll 中发现 阶段 3.check 中的执行队列里有回调函数,就马上去 阶段 3.check 里执行 setImmediate 的回调函数
如果在阶段 2.poll 中发现 阶段 3.check 中的执行队列里没有回调函数,等到阶段 1 中的时间到了之后从阶段 2 到 阶段 3 再到 阶段 1,执行 阶段 1.timers 队列中时间到了的回调函数
之后依次往复

所以此时 setImmediate 一定会在 setTimeout 和 setInterval 前执行,即便 setTimeout 和 setInterval 的等待时间是 0

ps:process.nextTick() 不属于 Eventloop 的一部分,意思是在哪个阶段执行 nextTick,就在这个阶段结束后马上执行

但是实际情况也可能是 环境 1.Eventloop 还没开启完毕
环境 2.v8 引擎确已经开启完毕了
执行 js 时,先把 setTimeout 和 setInterval 放到阶段 1.timers 的队列里
然后 Eventloop 开始阶段 1,就先去阶段 1 的执行队列里看有没有时间已经到的 setTimeout 和 setInterval,此时如果 setTimeout 和 setInterval 设置的时间是 0,就会直接执行,比 setImmediate 还早(因为还没到阶段 3)

所以结论是 setTimeout(fn,0)和 setImmediate 谁先执行是不确定的


微任务和宏任务

在 Node.js 环境:
setTimeout 属于宏任务,在 js 分析时会马上放到 timer 阶段的队列
setImmediate 属于宏任务,在 js 分析时会马上放到 check 阶段的队列
nextTick 属于宏任务,会马上放到当前阶段执行结束后
promise.then(fn) (一般来说 Promise 是由 nextTick 实现的) then 后的 fn 是在 resolve 的时候再放到当前队列后执行,属于微任务
await 转化为 promise.then 再分析,也属于微任务

在浏览器环境:
setTimeout 一会儿做,属于宏任务
promise.then(fn) fn 在 resolve 时放到微任务队列,属于微任务
await + fn 是 promise 的语法糖,可以转换为 fn.then(),而且 await 之后的代码也会包括在 then 里了, 属于微任务
(如果 fn 是一个 Promise 就是 fn().then(await 之后的代码),如果 fn 不是 Promise 就把他变为 Promise.resolve(fn()).then(await 之后的代码))

(setImmediate 只有 IE 浏览器中有,其他浏览器没有实现, nextTick 在浏览器中也没有实现,但其实有 MutationObserver、Object.observe(已弃用) 来代替微任务效果)


分享

Llane00
作者
Llane00
Web Developer