【筆記】到底 Event Loop 關我啥事?

初學者也能懂「為什麼 JavaScript 中存在事件循環(Event Loop)?」

--

若搜尋 Event Loop 或事件循環,會出現一堆說明「什麼是 Event Loop?」的內容;但鮮少有解釋「為什麼 Event Loop 存在?」的材料。就連搜尋「Why event loop exists」都查不到相關資料。但我認為如果它存在,而且被大量使用,那肯定很重要。這篇筆記就是要談 Event Loop 的重要性。

JavaScript and Event Loop (from: SessionStack)
本文淬煉自 Philip Roberts 在 JS Conf 的演講影片 What the heck is the event loop anyway?[PJCHENder - 筆記] 理解 JavaScript 中的事件循環、堆疊、佇列和併發模式[知乎] JavaScript 事件循环:从起源到浏览器再到 Node.js[掘金] 为什么要有事件循环机制(Event Loop)[Flavio Copes] The JavaScript Event Loop[SessionStack] How JavaScript works: Event loop and the rise of Async programmingAlpha Camp 部分教材

為什麼學 Web Dev 需要知道 Event Loop?

究竟 Event Loop 好用在哪?為何網路應用程式的開發者(Web App Developer)必須認識 Event Loop 呢?主要原因有兩個:

  1. Event Loop 協助了非同步請求的實現,並產生連貫的畫面呈現(influent UI)。
同步請求(Synchronous) v.s. 非同步請求(Asynchronous) (from: [Alpha Camp])
同步請求(Synchronous) v.s. 非同步請求(Asynchronous) (from: Alpha Camp)

2. Event Loop 將「費時較久」或「須等待事件才能啟動」的任務往後安排,因而能打造流暢的使用者體驗(Outstanding UX):

Outstanding UX (from: [QAD Blog])
Outstanding UX (from: QAD Blog)

以下簡單透過案例、起源及應用情境來說明「為何 Event Loop 很重要?」

你研究過 Web App 介面怎麼運作嗎?

想像一下 JavaScript 誕生前的瀏覽器,當使用者打開一個網頁、填完內容並提交表單後,進入等待中畫面,過了三十秒才傳來「某個地方內容有誤,請重新填寫」的提醒訊息。

如今透過 JavaScript,在前端介面就能協助用戶檢查表單填寫的內容格式,而不必透過網路經過伺服器端處理才回傳。起初,JavaScript 也確實是為了透過瀏覽器與網頁互動而被發明的程式語言。

JavaScript 為何一次只能執行一件事?

在 Google Chrome 尚未誕生的1990 年代,網路巨擘 Netscape 為了旗下同名的瀏覽器產品(為了啥?or請BE幫旗下同名),請當時任職的 Brendan Eich,設計一個「可提供更複雜的網頁互動」的語言原型——就是 JavaScript。

而為了讓開發者可以專注在程式開發上,JavaScript 被設計為「單線程(single threaded runtime)」——一次只執行一小段程式碼;而不必煩惱「並發性議題(concurrency issues)」。

為什麼畫面卡住了(blocking)?

但請試圖想像若瀏覽器「一次只執行一件事」,當所渲染(render)的資料來源為第三方 API,在瀏覽器等待回應(Response)時,畫面將停在奇怪的顯示階段。

Interface blocking from Pinterest
Interface blocking from Pinterest

因此 Event Loop 就在這樣的情境下因應而生,可以說它是依附著瀏覽器而存在的一個事件監聽器,用以控管各項任務順暢執行。當瀏覽器現階段有空閒時,才將排隊中的額外事件安排進去執行。(這是非常不專業但科普的說法,詳細原理會在本文後半說明)

Event Loop,你誰啊?

function of the event loop (from: SessionStack)
function of the event loop (from: SessionStack)

有趣的是,由於 Event Loop 是為瀏覽器而生,因此它並未出現在 EcmaScript 的標準中,反而是在 HTML Living standard of Event loops 中被定義:

To coordinate events, user interaction, scripts, rendering, networking, and so forth, user agents must use event loops as described in this section. Each agent has an associated event loop, which is unique to that agent.

這邊額外引用幾段有關 Event Loop 的清晰說明:

In general, in most browsers there is an event loop for every browser tab, to make every process isolated and avoid a web page with infinite loops or heavy processing to block your entire browser.

The Event Loop has one simple job — to monitor the Call Stack and the Callback Queue. If the Call Stack is empty, it will take the first event from the queue and will push it to the Call Stack, which effectively runs it.

Event Loop 與它的好朋友們

要完全理解 Event Loop 在瀏覽器中的行為,必須得先認識跟它有關的重要夥伴們:

Event Loop 與他的好朋友們 (from: SessionStack)

Call stack

一個後進先出( LIFO = Last In, First Out)的執行堆疊(call stack)。會依序執行函式:從全域(Global Scope)的主程式(main program)開始,逐一把各個函式推(push)至執行堆疊的最上方,並從最後進入的函式開始執行。當函式結束後(reurn),會將此函式抽離(pop off)堆疊。

Web APIs

瀏覽器提供了很多不同的 API(例如:DOM、AJAX、Timeout),讓我們能夠同時(concurrently)處理多項任務。當完成 Web APIs 的內部函式後(如 setTimeout()),便將任務傳遞至工作佇列。

Callback Queue

這是一個先進先出( FIFO = First In, First Out)的工作佇列(callback queue) 。會接收從 Web APIs 來的任務,並透過 Event Loop 的監控,當堆疊中沒有執行項目時,便把佇列中的內容拉進堆疊中執行。

跟著 Event Loop 動次動(手動操作範例區)

Philip Roberts 自己寫了一個方便視覺化瞭解 JavaScript Runtime, call stack, event loop, task queue 的工具 Loupe ,你可以把下面摘錄自各說明文章的例子,貼到他所提供的網站中執行。

Event Loop 簡單版

Example of a simple event loop

快速瀏覽過這串程式碼後,聰明的你應該能很快推測出執行結果:

foo
bar
baz

透過以下流程圖,我們可以明白完整的執行順序:

A simple event loop explanation (from: [Flavio Copes])
A simple event loop explanation (from: Flavio Copes)

Event Loop 進階版

接著我們花點時間思考一下,上面這支程式如何執行。應該可以得出以下結果:

Hi
Bye
cb1

透過以下 GIF 動畫,可以幫助我們理解執行流程的全貌:

Code execution in browser (from: [SessionStack])
Code execution in browser (from: SessionStack)

setTimeout 0 是在哈囉?

究竟 setTimeout(function(), 0) 是什麼意思?

Example of setTimeout 0

按照上述邏輯,setTimeout 也會被放進 Web API 中執行,並立刻(0 秒後)被丟進 callback queue 中。聰明的你應該可以推演出以下結果:

hi
JSConfEU
there

流程細節如下:

JavaScript Event loop with setTimeout 0 (from: PJCHEN der)

AJAX Request 像極了愛情

將同樣觀念套用到 AJAX Request 後,就能明白非同步請求的底層邏輯:發出請求後,不會立刻收到回應的特性,像極了愛情❤️。

Example of AJAX Request

聰明的你應該能舉一反三,得出以下結果:

hi
JSConfEU
{ "some": "json" }

詳細流程如下:

JavaScript Event Loop Ajax Request (from: PJCHEN der)

似乎還有個坑?!下回再攻略 ES6 Job Queue

JavaScript job queue and microtasks (from: [CareersJS])
JavaScript job queue and microtasks (from: CareersJS)

在資訊海中自由潛水一陣子之後,為了避免走火入魔、掉入萬劫不復的資訊深淵中,這次的探索只好先告一個段落。但看到許多文章都將 Callback Quene(又稱 Messege Queue)和 Job Queue (在 ES6 後誕生)同時比較與討論,因此敬請期待下回分解!Job Queue,好膽麥走!

--

--

郭耿綸 Kaleb
無限賽局玩家 Infinite Gamer | Publication

時而乘上浪峰、時而摔出浪板,靠著上帝才能一再拿起浪板、學著與這無限賽局共存的玩家。