JS

JS异步编程(2)-异步核心Event loop

2021-06-20  本文已影响0人  Johnson杰

Event loop 是 JavaScript 异步编程的核心,通过事件循环机制,让单线程的 JavaScript 具备异步处理任务的能力

异步任务队列

异步任务队列分为两类

为什么异步队列要分宏微任务?

其实在学习了 Event loop 很久之后,才突然反应过来,反问自己这个最初的问题
异步队列有一个就行了,已经能够满足异步操作的需求,为什么还需要分两种队列呢?

答案是:为了插队!

宏微任务的执行逻辑,本质上就是为了满足异步操作的插队需求,让某个后插入的异步操作尽量早的执行

宏任务(macrotasks)

API Web Node
DOM API
I/O
setTimeout
setInterval
setImmediate
requestAnimationFrame

有些地方会把 UI Rendering 也列为宏任务
但是在 HTML 规范文档中,发现这其实是和微任务平行的一个操作步骤

微任务(microtasks)

API Web Node
process.nextTick
MutationObserver
Promise.then catch finally

process.nextTick 和 web 端的 UI Rendering 类似

执行机制

Web 中的执行机制

浏览器环境下的 Event loop 是由HTML5规范明确定义,由各大浏览器厂商各自实现
这里主要涉及到下面几个浏览器线程:

基本流程

异步队列的执行机制,简单来说

  1. 当主执行栈里的任务清空之后,开始读取异步任务队列中的任务
  2. 先读取微任务队列中的任务,依次读取执行直至队列清空
  3. 然后从宏任务中读取第一个任务执行
  4. 从第2步开始重复,直到宏任务队列为空

同步任务 -> 全部微任务 -> UI Rendering -> 宏任务 -> 全部微任务 -> UI Rendering -> 下一个宏任务 -> ...

如果在执行过程中

操作触发的浏览器事件回调

// html
<div class="parent" onclick="handleClick()">
    <div class="child" onclick="handleClick()"/>
</div>

// js
function handleClick() {
    Promise.resolve().then(() => console.log('promise then'))
    setTimeout(() => console.log('setTimeout msg'), 0)
}

上面的代码,如果用户点击 child 元素
类似于用宏任务的触发方式,直接注册了 parentchild 元素的 click 回调函数
child click -> child promise then -> parent click -> parent promise then -> child setTimeout msg -> parent setTimeout msg

代码触发的浏览器事件回调

同样是上面的代码,如果使用 JS 代码触发事件

document.querySelector('.child').click()

那么和 dispatchEvent 类似,都是一种同步任务的触发方式
把两次的 click 事件都推入主执行栈队列
child click -> parent click -> child promise then -> parent promise then -> child setTimeout msg -> parent setTimeout msg

Node 中的执行机制

与 Web 端 Event loop 依赖浏览器线程一样,Node 端 Event loop 也依赖一位新同学: libuv

6个阶段

Node的 Event loop一共分为6个阶段,会按照顺序反复运行
每当进入某一个阶段的时候,都会从对应的回调队列中取出函数去执行
当队列为空或者执行的回调函数数量到达系统设定的阈值,就会进入下一阶段

image.png

每个细节具体如下:

  1. timers: 执行 setTimeout 和 setInterval 中到期的 callback,由 poll 调度进入该阶段
  2. pending: 某些系统操作级别的回调,在这个阶段执行
  3. idle, prepare: 仅在内部使用。
  4. poll: 执行 I/O 回调,在适当的情况下回阻塞在这个阶段。
  5. check: 执行 setImmediate 的回调函数
  6. close: 执行close事件的 callback
timers
poll

这一阶段主要处理两件事情

  1. 回到 timers 阶段执行回调
  2. 执行 I/O 回调

执行逻辑:


image.png

Node 10.x 及以前的基本流程

在 Node 10.x 及以前。Event loop 的每个阶段,都是先执行宏任务队列,再执行微任务队列
全部宏任务 -> 全部 nextTick 任务 -> 全部微任务

Node 11.x 及以后的基本流程

Node.js 在升级到 11.x 后,Event Loop 运行原理发生了变化。一个宏任务执行完成就执行微任务队列,和浏览器一致了
宏任务 -> 全部 nextTick 任务 -> 全部微任务 -> 下一个宏任务 -> 全部 nextTick 任务 -> 全部微任务

总结

在 Web 端,Event loop 依赖各个浏览器厂商的实现
除了正常的宏微任务外,还拥有独特的 UI Rendering 和 MutationObserver
依靠浏览器各线程的配合,完成 Event loop 的循环

而在 Node 端,Event loop 依赖 libuv 的实现,同时在 Node 11 版本前后有差异
Node 端拥有 6 个事件阶段,每个阶段都可以进行 Event loop 循环

参考文章

Tasks, microtasks, queues and schedules
一次弄懂Event Loop(彻底解决此类面试问题)
面试题:说说事件循环机制(满分答案来了)

上一篇下一篇

猜你喜欢

热点阅读