Event loop 与 Async Await 比较

Node.js Event loop 与 Async Await 比较

JS 原理

JS 为何是单线程的

最初设计JS时,主要是用来验证浏览器DOM元素的一个语言,如果js是多线程,那麽两个线程同时对一个DOM元素会进行相互冲突的操作 这样浏览器是无法执行的

JS 为何需要非同步

如果JS没有非同步,那麽程式由上而下执行,如果遇到上一行解析时间很长,那麽下面的代码就会被阻塞。对用户而言阻塞=卡死。就会有很糟的体验

JS 单线程是如何实现非同步的呢

JS 的非同步以及多线程都可以理解为一个假象。因为其实就是透过事件循环(Event Loop),去调配单一线程的执行顺序。 所以理解Event Loop 就可以理解JS非同步的运行机制。

Event Loop

为何要弄懂 Event Loop

  • 掌握底层原理,可以让自己以不变应万变
  • 增加深度,了解JS运行机制

Stack、Queue、Heap

堆 Heap

堆为一种数据结构,利用而二元树所获得的一种数据结构。一种为最小堆,另一种为最大堆

堆叠 Stack

Call Stack

  • 一种资料结构
  • 后进先出的概念
  • 只能在最尾端插入后删除的线性表

Callback Queue or Event Queue

  • 一种资料结构
  • 先进先出的概念
  • 在结构的前端进行删除,在结构的后端进行插入
  • 没有元素时称为空阵列

任务种类

  • 一种是宏任务(MacroTask)
  • 一种是微任务(MicroTask) 不管宏任务or微任务都是进入 Event Queue
宏任务(MacroTask)
  • script 全部代码
  • I/O
  • UI Rendeting
  • MessageChannel
  • postMessage
微任务(MicroTask)
  • Process.nextTick(Node.js)
  • Promise
  • Object.observe(废弃)
  • MutationObserver
queues 非同步事件
  • setTimeout、setInterval、setImmediate
  • click
  • ajax

调用线怎麽走的

JS调用执行线为 后进先出(Stack) 的规则,当函数执行时,会被添加到线的顶部,当调用执行线执行完成后,就会从顶部移出,直到线内被清空

Event Loop 进程

  1. 「调用执行线」执行完同步任务后,会去看调用执行线是否为空
  2. 如果调用执行线为空,会去检查微任务是否为空
  • 为空:执行宏任务
  • 不为空:执行微任务
  1. 每次单个宏任务执行完毕后,检查微任务(Callback Queue)队列是否为空,不为空则依「先入先出」规则做
  2. 设置微任务队(Callback Queue)为 null,然后执行宏任务,如此循环

microTask 进程

  • 设置 microTask 检查点标誌为true
  • 当事件循环 microtask 执行不为空时:选择一个最先进入 microtask 阵列的 microtask。将事件循环 microtask 设置为以选择的 microtask,运行此 microtask,将已经执行完的 microtask 为null,移除此 microtask
  • 清除 indexDB 事务
  • 设置进入 microtask 检查点的标誌为 false

分析 Event Loop

  1. 不同类型的任务会进入到对应的Event Queue
  • setTimeout or setInterval 会进入宏任务的Event Queue
  • Promise or process.nextTick会进入微任务的Event Queue
  1. 遇到 promise.then 之类的微任务,会让这个微任务被安排在当前的宏任务中
  2. 当执行完一轮的同步+非同步脚本后,就往下一轮迈进

async/await

async/await 是什麽

我们创建了promise但不能同步等他执行完成。我们只能通过 then 传一个 callback function,这样很容易再次陷入 callback 地狱。 所以实际上 async/await 在底层转换了 Promise and then 的回调函数。也就是 Promise 的语法糖

  • async 是非同步的缩写
  • await 是等待非同步方法执行完成

async/await 用来做啥 优化 promise的回调问题

async/await 内部做了什麽

  • async 返回一个 Promise 物件,如果再函数中 return 一个直接量,async 会把这个量通过 Promise.resolve() 封装成 Promise 物件。 如果你返回 Promise 就以你返回的Promise为主
  • await 是等待,等待运行的返回值。await 后面通常是一个 Promise。但这不代表 await 后面只能跟非同步操作

await 等待机制

如果后面接了 Promise,就会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 值作为 await 表达式的运算结果

async/await 在什麽场景下使用

如果由 多个 Promise 组成的 then 链 时,就可以使用

async/await 分析方向

  • async 定义的是一个 Promise 函数,和普通函数一样只要不调用就不会进入事件阵列
  • async 内部如果没有主动 return promise,那 async 会把函数的返回值用 Promise 包装
  • await 必须出现在 async 函数内,await 不一定要跟一个非同步操作,也可以是一个普通表达式
  • 遇到 await 关键字,await 右边的语句会立即被执行,然后 await 下面的代码进入等待状态,等待结果
  • await 后面如果不是 promise对象,await 会为阻塞后面的代码,先执行 async外面的同步代码,同步执行完后,再回来 async 内部 ,把这个非 promise 的东西,作为 await 表达式的结果
  • await 后面如果是 promise,await 也会暂停 async 后面的 code,先执行 async 外面的代码,等着 Promise 对象 fulfilled, 然后把 resolve 的参数作为 await 表达式的运算结果

分析原则

  • 遇到 new Promise 是直接执行其任务
  • new Promise 接的 then 是微任务
  • await 会等到 async 内的事情都做完才往下做同个 function 内的事情
  • await function 会去驱动这个 function,function 做完,再往同层的同步走下去
  • async 回传 Promise & async 中的 return 会被当作 resolve value
  • 下面这串算同步指令
await new Promise((res){
    console.log('1')
})

参考资料