JS中的Event Loop (Macrotask與Microtask)

要想搞明白Javascript的Event Loop我們首先要了解Javascript的運行環境的運行機制。

Javascript是單線程的

我們都知道JS的最大特點是單線程,這就意味著所有的任務需要排隊,后一個任務執行,需要等待前一個任務執行完畢,如果前一個任務執行時間比較久,后一個任務就需要一直等待。等待的過程是可能是處理跟不上或者是硬件I/O效率(比如Ajax操作從網絡讀取數據)導致的。
Javascript語言的設計者為了解決這個等待問題,讓主線程執行的時不管I/O,掛起處于等待中的任務,先運行排在后面的任務。等到I/O返回了結果,再回過頭,把掛起的任務繼續執行下去。這就是異步任務的執行機制。這樣,Javascript就有了同步任務和異步任務兩種。

任務隊列(Macrotask queue和Microtask queue)

Javascript中的同步任務都是在主線程上執行的,異步任務則是由瀏覽器執行的,不管是AJAX請求,還是setTimeout等 API,瀏覽器內核會在其它線程中執行這些操作,當操作完成后,將操作結果以及事先定義的回調函數放入JavaScript 主線程的任務隊列中。

在Javascript的Event Loop機制中,存在兩個任務隊列:Macrotask queue和Microtask queue。

  • macrotasks: setTimeout, setInterval, setImmediate, I/O, UI rendering
  • microtasks: process.nextTick, Promises, Object.observe(廢棄), MutationObserver

Macrotask還是Microtask?

可以這樣簡單理解:如果你想讓一個異步任務盡快執行,那么就把它設置為Microtask,除此之外都用Macrotask。因為,雖然Javascript是異步非阻塞的,但在一個事件循環中,Microtask的執行方式基本上就是用同步的。

Basically, use microtasks when you need to do stuff asynchronously in a synchronous way (i.e. when you would say perform this (micro-)task in the most immediate future). Otherwise, stick to macrotasks.

可能存在的問題

相信讀到這里你已經意識到,如果一個Microtask隊列太長,或者執行過程中不斷加入新的Microtask任務,會導致下一個Macrotask任務很久都執行不了。結果就是,你可能會遇到UI一直刷新不了,或者I/O任務一直完成不了。

或許是考慮到了這一點,至少Microtask queue中的process.nextTick任務,是被設置了(在一個事件循環中的)最大調用次數process.maxTickDepth的,默認是1000。一定程度上避免了上述情況。

Event Loop

按照 WHATWG 規范,每個Event Loop周期內,會經歷如下步驟:

  1. 檢查Macrotask queue,把最先入列的Macrotask壓入執行棧(每個Event Loop周期內只會執行一個Macrotask),開始執行,如果Macrotask queue為空,跳到步驟3。
  2. 待該 Macrotask執行完畢后,清空執行棧。
  3. 遍歷執行Microtask queue中的Microtask。遍歷這些 Microtask 的過程中,還可以將更多的 Microtask加 入Microtask queue,它們會一一執行,直到整個 Microtask 隊列處理完。
  4. 執行完Microtask queue中的所有Microtask后,回到步驟1。

以上就是Event Loop的循環機制。

參考
Understanding the Node.js Event Loop

理解 Node.js 事件循環

Difference between microtask and macrotask within an event loop context

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容