此為 event loop 系列文章 - 第 3 篇:
- Javascript 中的 event loop 及瀏覽器渲染機制
- 從程式碼角度來看 event loop
- 使用原生的 queueMicrotask 處理微任務
- Vue.nextTick() 中的 event loop
前言
在研究 event loop 的過程中,赫然發現原來瀏覽器已經有了原生的 queueMicrotask 讓開發者可以自行管理微任務的執行,下面我們來看看什麼情況下會需要用到這個功能吧
基本用法
基本上 queueMicrotask
就如同使用 new Promise()
一樣,會將 callback 加入到 微任務佇列 (microtask queue)
1 | let callback = () => console.log("Regular timeout callback has run"); |
結果:
1 | 'Main program started' |
何時會使用到 queueMicrotask
基本上大部分的狀況下,開發者們都不太需要使用到 queueMicrotask
,但 MDN 上提到一個有趣的範例,為了保證程式的執行順序,在某些狀況下適合使用到 queueMicrotask
將某些任務的優先程度提高,提前執行
假設有一個 DOM 元素添加了自定義的 load
事件,當執行這個元素的 getData()
方法後會利用 dispatchEvent 送出一個事件,通知資料已經載完了(第 1 行)
1 | element.addEventListener("load", () => console.log("Loaded data")); |
1 | customElement.prototype.getData = (url) => { |
由於 dispatchEvent 發出的事件是同步執行的,並不像一般 WebAPI 原生事件(click, mouseenter 等) 是異步執行的 宏任務 (macrotask) ,所以以上程式碼的執行順序如下:
- 首先執行第 2 行,印出 Fetching data…
- 執行第 3 行的
getData()
函式,第一次執行getData()
時由於沒有 cache 所以會執行第 6 行的fetch
等待資料下載完成 - 等待
fetch
異步執行獲取資料的過程中,第 4 行會先執行印出 Data fetched - 最後資料載入完成後執行
this.dispatchEvent(new Event("load"));
,回到第 1 行的 callback 印出 Loaded data
結果
1 | 'Fetching data…' |
但如果之後執行 getData()
資料已經有 cache 的狀況下,執行過程如下:
- 首先執行第 2 行,印出 Fetching data…
- 執行第 3 行的
getData()
函式,此時執行getData()
時由於已經有 cache 所以會直接執行到第 4 行的this.dispatchEvent(new Event("load"));
發出 load event - 由於
dispatchEvent
是同步執行的,第 1 行中的 callback 會立即執行印出 Loaded data getData()
函式結束,接著執行第 4 行印出 Data fetched
結果
1 | 'Fetching data…' |
哇!我們可以看到有無 cache 時,會導致程式碼的執行順序不一,這一定不是我們期望看到的,此時就可以考慮使用 queueMicrotask
(第 3 行),使原本的同步事件改為異步事件執行,這樣不論有無 cache 的狀況下都可以保證一樣的執行順序
1 | customElement.prototype.getData = (url) => { |
結果
這樣不論有無 cache 都會是
1 | 'Fetching data…' |
小結
一開始覺得 MDN 提出的這個範例蠻有趣的,在這種場合下用 queueMicrotask
就可以保證不論是否有 cache 的狀況下都有一致的執行順序,但我後來想想這個範例寫得似乎有點奇怪,仔細想想通常在使用 fetch
呼叫 api 時都會添加 await
等待資料回傳回來再執行後面的程式,如此一來應該就不必特別用到 queueMicrotask
了吧?
1 | element.addEventListener("load", () => console.log("Loaded data")); |
1 | customElement.prototype.getData = (url) => { |
結果
1 | 'Fetching data…' |
不論有無 cache 應該都會是這個順序,但或許 MDN 上的範例想要的執行順序是 'Data fetched'
在 'Loaded data'
前執行,如果是這樣的話就的確需要用到 queueMicrotask
了