Photo by Sam Moqadam on Unsplash
目標
弄懂 EventLoop 相關的名,並可以用簡短的話來敘述
0618 EDIT: setTimeout 跟 promise 的執行順序比較
參考
- JavaScript 中的同步與非同步(上):先成為 callback 大師吧!
- Philip Roberts: What the heck is the event loop anyway?
- Erin Zimmer: Further Adventures of the Event Loop
- node.js - about
- 從ES6開始的JavaScript學習生活-異步程式設計與事件迴圈
強烈建議: 觀看完2與3的影片與1的教學文章。
底下筆記僅記錄個人學習上的心得,有使用到非常多影片中的截圖做為輔助。
名詞
- EventLoop 事件循環
- 單執行序 Single Thread
- WebAPI
- 阻塞 Blocking / 不塞 non-blocking
- 同步 synchronous / 非同步 asynchronous
- 呼叫堆疊 Call Stack
- 事件佇列 Event Queue / Callback Queue
- 微任務 Microtask
- 宏任務 Macrotask
前言
作為一個前端攻城屍,開發的過程中很容易會碰到一些名詞,像是什麼 Single Thread、V8 (JavaScript engine)、Node.js、還有最常碰到的同步非同步等奇怪的專有名詞。而且面試的時候還超級喜歡問這些問題的,筆者也是在某個面試被問到之後,發現自己沒辦法好好地用自己的語言敘述,所以就誕生了這篇文章!
而關於理論與實作的抱怨不在今天的主題討論,但相信能多一些知識也是好的。
主題
JavaScript 是一個 Single Thread 程式語言,可以在不同的 Runtime (執行環境)上執行,像是各家瀏覽器、node.js等。為了更增進使用者體驗,能透過使用 web API,非同步地不阻塞畫面運行,達成程式運行更順暢的效果。而其中任務的執行順序機制,即是 EventLoop。
Single Thread & Runtime
- Single Thread: 單一執行續,指的是一次只能做一件事
- Runtime: 執行環境,像是各家瀏覽器的JS引擎&Node.js
- web API: 執行環境提供,與之互動的方式
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.
雖然JS是單一執行續,但 RunTime 可以不是,因此可以叫瀏覽器幫忙做一些事情,完成之後通知。
就像是自己去買晚餐的時候,點完單跟老闆說我要去隔壁買飲料,待會再回來拿。
自己只有一個人,所以最有效率的狀況就是叫熊貓,先點單然後去做其他事,飲料買完之後再回來拿便當。
而不同執行環境,實作的webAPI也不一樣,就像各家瀏覽器有不一定相同的方法名稱。
如果同一份code同時跑在瀏覽器 & node.js上? 當然有機會壞掉
Heap / Stack
Heap
- 處理記憶體分配,ex: 變數
Stack (Call Stack 呼叫堆疊)
- synchronous 同步執行
- 反向執行: A 呼叫 B 呼叫 C => 執行順序: C -> B -> A
Blocking / Non-blocking
阻塞 Blocking
- 畫面停頓 / 卡住
- 背景程式正在跑,需要時間等結果出來
- 等洗衣機 / 等鹹酥雞
阿是要等多久!
不塞 non-blocking
- 等待的時候先去做其他事,完成之後通知
- 等洗衣機逼逼叫之後再去處理 / 阿我等一下再回來拿
餐點呼叫器
Synchronous / Asynchronous
同步 synchronous / 非同步 asynchronous
Blocking methods execute synchronously and non-blocking methods execute asynchronously. --- node.js
會阻塞的方法以同步方式執行,不會造成阻塞的方法以非同步執行
Synchronous 同步執行
典型的一件事做完後下一件,若正在執行的任務比較耗時,就會造成阻塞。
全世界都在等你
Asynchronous 非同步執行
即點完單人就跑去做其他事,通常會這麼做是因為這件事需要耗時比較久,不想空等,而趁等待的時候可以做其他事情。
從中文字面滿容易誤解,ex: "同步"去做一些事情
這邊提供筆者自己的記憶方式: 同步理解為同步調、同節奏,像是當兵的時候要對腳步1212。這時候你不可能,一邊等任務一邊跑去打小蜜蜂,同時做兩件事就是不同步 脫離部隊掌握
因此實務上通常使用在會需要等回應的地方,例如請瀏覽器5秒後通知你 (setTimeOut),或者打API等Server回應。取得回應之後再去執行 CallBack,才不會造成畫面卡在那邊一直轉圈圈。
Event Queue
事件佇列 Event Queue / Callback Queue / task Queue
不同名詞,描述同一個概念: 非同步的任務放進去 queue
非同步的任務,不直接進入 Stack 執行,而會進到 Queue 等待(到stake可以執行的時候才過去)。
不同runTime可能會有不同的 queue,但同類(依照runTime自己區分)的方法會進到同個 queue
- asynchronous
- 可以有很多的 queue
- queue 會等到 stack 做完拿過去 stack執行
EventLoop
非同步Task 是怎麼運作的?
一種機制: 隨時檢查 Stack,當其內容為空時,取出 Queue 內任務拿進去 Stack 執行
簡化示意會像這張圖內的程式碼:
此外,如果在瀏覽器上執行,還需要處理頁面渲染。瀏覽器很聰明,不會時時刻刻都去重畫圖,通常是一段時間才刷新畫面(ex: 60 fps -> 16 ms 更新一次),也不可能打斷正在執行的工作,所以重新渲染的條件是: 單一任務完成,且到達渲染時間
由於這樣的機制,如果在做耗時較久的任務時,渲染就會被阻塞。
圖中大水管指的是Browser rendering pipeline,而渲染被"大任務"卡住住到21ms還沒更新
圖片出自-參考3
注: 非同步的任務才會進到 queue 內,同步的任務會在stack就處理完了。但是非同步的callback裡面是可以裝同步的任務,也可以裝耗時很久的任務,渲染一樣會被卡住。
setTimeOut(() => {
while(true) {}
}, 500)
額外主題: setTimeout 不一定依照設定的時間執行
僅代表"盡快"在delay內執行
Macrotask / Microtask
宏任務 Macrotask
- 非 Microtask 的任務
不過就是出個名詞出來嚇人,網路上有些資源不使用這個詞,而直接使用 task
微任務 Microtask
- 99.9% 是 promise
- 接續在每個 Macrotask 之後做
- 整個 Microtask queue 做完才能渲染
圖片出自-參考3
注:被微任務卡住渲染
子主題: setTimeout 與 Promise 的執行順序
- setTimeout 放在queue裡面
- Promise 放在 microtask
- microtask(同步) -> microtask(非同步) -> queue(非同步)
Promise和setTimeout执行顺序
async await、Promise、setTimeout执行顺序
Promise 实现原理
Animation Frame callback queue
- 跟 Microtask 一樣有特殊行為
- 接續在每個 Macrotask 之後
- 在頁面渲染之前做
- 當前 queue 裏頭的做完就會渲染 (而非整個queue做完)
後續可以設定新的動畫frame,而不卡住渲染
結語
這篇文章不知道會不會太長沒人看XD