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')
})

參考資料