[進階 js 12] Event Loop


Posted by tzutzu858 on 2021-03-31

這篇文章主要是做 所以說 event loop 到底是什麼玩意兒?| Philip Roberts | JSConf EU 這個影片的筆記,這篇文章所放的圖片也是影片擷取出來

JavaScript , What are you ?

JavaScript 是個單執行緒的程式語言,裡面有
Call stack (呼叫堆疊)、Event Loop (事件循環)、Callback queue (回調佇列) 和一些 API 等等


那 V8(Google 開發的開源 JavaScript 引擎)也有 Call stackEvent LoopCallback queue 和其它 API 嗎?
V8 說我有 Call stackheap,其他是什麼?(疑!!!)

來看一下 JavaScript 的 Runtime, 像是 Chrome 與 Node.js 都使用了 V8 引擎

JavaScript Runtime的簡化示意圖

V8 有 Heap(memory allocation),call stack(execution contexts)
但當你用 setTimeout 或是 DOM 或是 HTTP 請求的東西,那些並不存在在 V8 裡
所以不存在 V8 ,那他們又是怎麼運行的?


那些像是 DOM、AJAX、setTimeout、Event Loop、callback queue 之類的東西,都是 web API ,由瀏覽器所提供額外的東西

就讓我們從頭講起吧

What is the call stack

one thread == one call stack == one thing at a time

Call stack 是執行堆疊,在調用多個函數的腳本中,跟踪其位置的機制。
一旦完成該功能,它將從 stack 的頂部移走,直到沒有剩餘為止。


blowing the stack

也許你有聽過 blowing the stack,像是下面例子

function foo () {
    return foo ();
}

foo();


然後 Chrome 會說 :

RangeError: Maximum call stack size exceeded


blocking

blocking 沒有一個嚴格的定義
它其實就是一段很慢的程式
跑起來很慢、同時又在 stack 上的東西就是 blocking

var foo = $.getSync('//foo.com');
var foo = $.getSync('//bar.com');
var foo = $.getSync('//qux.com');

console.log(foo);
console.log(bar);
console.log(qux);

如果上述是同步請求怎辦
那這樣呼叫 $.getSync('//foo.com') ,然後網路請求,等待回應...
好了以後再呼叫 $.getSync('//bar.com') ,然後網路請求,等待回應...
好了以後再呼叫 $.getSync('//qux.com') ,然後網路請求,等待回應...
最後好不容易那三個 blocking 已完成,可以清空 stack,再做 console.log(foo)console.log(bar)console.log(qux)

在單一執行緒的程式語言並不能像 Ruby 那樣使用多執行緒,我們做了一個網路請求就只能等它跑完為止,因為並沒有處理這種情況的方法
那如果我們有同步請求,那我們就不能做其他事,無法顯示畫面,無法跑其他程式碼都卡住了,但我希望使用者看到流暢 UI ,那我們就不能 block the stack

最簡單的解法就是

asynchronous callbacks


瀏覽器中幾乎沒有 Blocking ,在 Node.js 也是一樣,都是 Async (非同步)
基本上就是執行一些程式碼然後 callback,然後再執行,那實際上是怎樣呢?

console.log('a')

setTimeout(
    function cb(){
        console.log('b')
    }, 1000)

console.log('c')

按照同步程式的執行順序,應該是 a -> b -> c 這個結果,但真正的結果是 a -> c -> b,那到底發生了甚麼事?

畫面不夠清楚可以直接到 loupe 網站來執行看看
loupe 是視覺化的呈現 JavaScript 的 call stack / event loop / callback queue 如何相互影響,有視覺化真的比較好理解

這基本上就是 Event Loop 在 Concurrency 上會發揮作用的地方

就算把上述例子的 setTimeout 變成 0 呢 ? 那執行順序會是如何 ? 結果依然是 a -> c -> b
不要忘了 Event Loop 的特性是必須等到 stack 清空後才可以把 callback 放到 stack ,才繼續執行 console.log('b')


今天就算是執行 AJAX ,它也是會放到 web apis ,在 XHR 等待回應前,或是永遠不會完成也沒關係,stack 都可以順利繼續執行程式碼不會塞住。假設執行完,它就會被放到 task queue,然後再被 Event Loop 抓到 stack 裡。


回顧為何要有 Event Loop ?

一開頭便講了 JavaScript 是個單執行緒的程式語言,一次只能做一件事,你在做其他程式時,他無法做出 AJAX 請求,你在做其他程式時,他無法做出 setTimeout 的請求,讓我們能夠同時做事的原因是 :

瀏覽器不只有 Runtime ,瀏覽器會提供我們其他東西,那些都是你無法取得的執行緒,你只能呼叫它們

而當那些 Web Apis 執行完,它就會被放到 task queue。
之後 stack 被清空後,task queue 裡的事件就會被依序推回 stack。

這個偵測 stack 為空,便把 task queue 內的事件推回給 stack 的機制就稱為 Event Loop。

簡單來說 Event Loop 的工作在於 :

  1. 查看 stack 和查看 task queue
  2. 如果 stack 是空的,他就會把位於 task queue 的第一個東西放在 stack,讓它 run

參考資料 :
所以說 event loop 到底是什麼玩意兒?| Philip Roberts | JSConf EU
JavaScript 中的同步與非同步(上):先成為 callback 大師吧!
異步程式設計與事件迴圈
What is an event loop?
Concurrency與Parallelism的不同之處










Related Posts

如何在 Mac 上印出文字版的資料夾結構

如何在 Mac 上印出文字版的資料夾結構

W21_直播檢討

W21_直播檢討

Day01 : 數字、字串、變數

Day01 : 數字、字串、變數


Comments