iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 24
1
Modern Web

JavaScript基本功修煉系列 第 24

JavaScript基本功修練:Day24 - 非同步執行與事件佇列

在最後這個星期要開始進入同步與非同步的課題了,在進入語法部分,例如常用的Promiseasync/await之前,我們要先理解非同步函式是如何被執行。

這篇文章會整理以下知識:

  • 需要非同步執行的原因
  • 函式實際運作流程
  • 常見非同步例子 - XMLhttprequest
  • 總結同步與非同步

需要非同步執行的原因

我們都知道JavaScript是單執行緒,意思是它「一次只能做一件事」,就像一心不能二用,要把一件事做完才做下一件事。但按此原則,當執行時遇上setTimeoutXMLhttprequest這些需要時間執行的情況時,就會導致阻塞,逼使我們一直乾等它們完成才可以往下跑其他程式碼,那麼下面的功能(例如點擊按鈕、表單功能等等)基本上都是報廢了。

因此,我們需要一個運作方法去處理這些耗時的程式,那就是非同步的執行方式。非同步的方式就是先把這些會阻塞運作的程式,先移到一個叫「事件佇列」的地方,過某個時段後,才回傳它並且執行它

具體例子:

console.log(1); //1

//setTimeout是需要非同步執行,所以先不執行
setTimeout(function cb(){
    console.log(3)}, 3000) //3
    
console.log(2) //2

有一點要注意,並非所有回傳函式會是非同步,回傳函式可以是同步,所以並非所有回傳函式都會被移到事件佇列,同步的回傳函式是可以直接在呼叫堆疊被執行掉

函式被執行時的實際流程

所以我們現在知道,那些耗時的程式會被採用非同步的方式執行。而同步程式就會直接在呼叫堆疊被執行掉。實際流程如下:

  1. 呼叫堆疊(Call Stack)
    所有同步執行的程式都會放到這裏被執行,這裏的資料是後進先出
  2. Web APIs
    需要等待的非同步程式會被放到Web API,在這裏進行等待(如等待計時完結、等待資料回傳)的動作
  3. 事件佇列(Event Queue)
    等待結束後的非同步程式,就會被加入事件佇列,這裏的資料是先進先出
  4. 當呼叫堆疊處理完所有同步程式,變得空空如也時,在事件佇列裏的函式就會被移回呼叫堆疊執行

注意:

  • JS引擎本身沒有Web API事件佇列,這些東西是屬於瀏覽器本身。
  • 等待動作不是在事件佇列進行,而是在Web APIs,等待完成(如等待計時完結、等待資料回傳)後才會移到事件佇列

什麼是呼叫堆疊(Call Stack)?

JS遇到函式時,會把它推進呼叫堆疊,執行完後就推走。

什麼是Web API?

Web API是瀏覽器本身有提供的API。例如是:

  • 控制DOM元素的getElementById
  • 計時器setTimeoutsetInterval
  • AJAX抓資料用的XMLHttpRequest

動畫流程

loupe這個網站具體模擬了上面的運作情景,它更具體地描繪了整個非同步執行的過程。

當你看完loupe預設例子的動畫後,你會留意到第一行$.on('button', 'click',...)會一直留在Web APIs裏,因為它是在等待點擊事件,只要點擊沒有觸發,它就會一直留在直留在Web APIs裏等待,不會移到事件佇列,甚至最後移到呼叫堆疊裏被執行。

這就是為什麼大部分DOM元素都是設計成非同步執行,因為它們通常都需要等待事件被觸發,才會回傳函式,並執行函式。

Event Loop(事件迴圈)

Event loop(事件迴圈)負責把事件佇列裏的函式推回呼叫堆疊執行。它會檢查呼叫堆疊是否已經被清空,如果是,就會依序把儲存在事件佇列(Event queue)的函式推到呼叫堆疊,並且執行它們。

常見非同步例子 - XMLhttprequest

除了setTimeout這類計時器的例子外,AJAX技術也是常常會用到非同步的執行方式。AJAX技術透過使用XMLhttprequest物件,使我們不用刷新畫面,也能與伺服器互動,包括提出請求,以及回傳資料。

因為抓取資料是需要時間,也會因應網絡狀況而延長,所以使用XMLhttprequest時,我們可以在第三個參數設定true,意思是用非同步方式取得遠端資料。

範例如下:

const xhr = new XMLHttpRequest();
xhr.open('get','https://randomuser.me/api/',true);
xhr.send(null);

xhr.onload = function(){
    console.log('執行次序:2') //執行次序:2
    console.log(xhr.responseText) //取得遠端資料
}

console.log('執行次序:1') //執行次序:1

圖解:

總結同步與非同步

同步程式

  • 由上到下順序被執行
  • 它們會按次序放入事件堆疊,後進先出
  • 我們可以完全掌握任務的順序

非同步(異步)程式

  • 它會先被移往Web API,等待結束後再被移到事件佇列(event queue)
  • 在事件佇列的非同步函式,需要等所有在事件堆疊裏的同步程式都被執行完後,才能移往呼叫堆疊裏被執行
  • 從事件佇列到呼叫堆疊的次序是先進先出
  • 不能保證執行順序,這會因應執行時的各種情況而不同
    (例如XMLhttprequest取得資料的速度是因應網絡及伺服器的狀應而定)

明天會繼續整理有關非同步常用語法的概念與運用方法,感謝你的閱讀~ 還沒跑完鐵人賽的朋友,我們一起加油!!

參考資料

異步程式設計與事件迴圈
JS 原力覺醒 Day13 - Event Queue & Event Loop 、Event Table
一次只能做一件事情的 JavaScript,解釋 Event queue 怎能不用動畫呢
JavaScript's Call Stack, Callback Queue, and Event Loop


上一篇
JavaScript基本功修練:Day23 - 閉包
下一篇
JavaScript基本功修練:Day25 - Promise
系列文
JavaScript基本功修煉31

尚未有邦友留言

立即登入留言