2020年9月17日 星期四

非同步處理以及事件循環,「What the hack is the event loop anyway?」Review

關鍵字:Call Stack、Task Queue、Event Loop


前言

  在尚未了解何謂Event Loop、Call Stack、Task Queue等等的概念之前,先來看看下列的程式碼。下面三行的程式碼以直覺去猜執行順序可能會是1→2→3,但實際上按下Run之後的結果卻是事與願違的1→3→2,發生什麼事?

以下請搭配影片服用:


Javascript的執行方式 - 單線程(Single Threaded )

  首先要先了解的是,JS是一個「單線程」程式語言,也就是會將程式碼丟入呼叫堆疊(Call Stack)後一步一步執行,即使遇到比較耗時間的程式碼(例如迴圈、timout)也還是會乖乖執行完才往下一段程式碼進行。


呼叫堆疊(Call Stack)

  呼叫堆疊是一種紀錄目前我們在程式碼當中哪個位置的資料結構,以一段程式碼來舉例,當我們呼叫函示時會將這個函式加到堆疊的頂端,當回傳結果後則取下頂端的函式。

  以影片中的函式來舉例,堆疊的最底層是程式本身main(),首先我們先呼叫printSquare(4),此時將printSquare(4)加入到堆疊頂端,printSquare(4)當中呼叫square(n),將square(n)加入到堆疊頂端,square(n)呼叫multiply(n, n),將multiply(n, n)加入到堆疊頂端;當multiply(n, n)回傳n * n的值之後,將堆疊的頂端移除,接下來的每一項都進行同樣的動作,這就是呼叫堆疊的基本運作。

同步(synchronous)與非同步(asynchronous)
  同步與非同步在開發過程中必定會遇到的問題,什麼是同步?聽起來同步應該要可以處理一堆事情啊?事實上在開發環境中恰恰好是相反的結果,用白話來講,同步比較像是「同樣步調」,而非「同時處理」,就像現實情境中你可以要求跑很快的人跑慢一點,但你沒辦法強迫一個跑很慢的人跟上第一名的腳步,所以當我們想要得到「同步」的結果時,就需要配合「跑得慢的人」的步調。非同步則是相反,我們可以透過函式的呼叫以及堆疊的概念來「非同步處理」我們的程式,讓跑得快的人先到終點,跑得慢的人則照著他的步調慢慢來。


阻塞(Blocking)
  當我們需要「同步處理」我們的程式碼時,遇到執行上需要花比較長時間的程式碼而造成頁面「卡住」時,我們就會稱這樣的情況為阻塞(Blocking)。以上述的情境為例,同步處理下我們必須讓每個人一個一個上跑道跑步,當中間有「跑得慢的人」時,我們要等他跑完才能讓下一位上跑道,在這期間我們什麼都不能做,只能等他跑完,我們就會稱這段期間發生了「阻塞」。


事件循環(Event Loop)和任務佇列(Task Queue)
  講到這裡,我們終於可以回到前言,為什麼我們可以透過事件循環(Event Loop)和任務佇列(Task Queue)來達成非同步處理(Asynchronous Callback),但我們還是要先解釋什麼是事件循環和任務佇列,以及上述我們提到的一堆名詞之間的關係。

同樣我們使用影片的例子

console.log('hi');
setTimeout(function cb(){
    console.log('there');
}, 5000)
console.log('JSConfEU')

此時若執行的方式為同步處理,我們必須照順序從console.log('hi') → setTimeout() //等五秒 → console.log('JSConfEU')依序執行;當以非同步處理去執行時,執行順序同樣是console.log('hi') → setTimeout() → console.log('JSConfEU'),但要注意,當執行到setTimeout()時,程式會交由瀏覽器的「Web APIs」去做處理,並視setTimeout()已完成,而進入到console.log('JSConfEU'),此時的堆疊狀況如下圖,

程式碼繼續執行,setTimeout()則交由Web APIs去執行,當setTimeout()倒數結束之後並不會直接加到堆疊當中去執行,而是先進入任務佇列(Task Queue)暫緩一下。程式碼執行完也代表堆疊已經被清空,這時今天的主角「事件循環(Event Loop)」就出現了,他會去檢查堆疊狀況,如果堆疊是空的,他會將任務佇列的第一項堆到堆疊上,到此,我們期望的「非同步處理」就達成了。


結語
  非同步處理對於像我這樣的JS初心者來說是相對抽象的概念,我可以簡單理解「哦!同步都是一步一步來所以結果會相對比較慢,非同步則是相反」而去寫一個目前來說還可以接受的程式碼和網頁,但在非同步處理的當下到底發生什麼事是完全沒辦法去想像和推測的,在了解堆疊、任務佇列和事件循環之間的關係之後會對於自己程式當中的優化是非常有幫助的,畢竟開發者所追求的是良好的使用者體驗和順暢的UI介面。






沒有留言:

張貼留言

個人筆記④:MongoDB資料庫、Mongoose套件

軟體需求: MongoDB 、 Robo 3T 透過NPM安裝的套件: Mongoose 本頁面目錄: 安裝Mongoose以及設定資料庫連線 建立Model以及種子資料