本篇筆記來自 2020.2.19 六角學院線上研討會,講者為偷米騎巴哥的 Tommy。在這一次講座中,Tommy 老師用許多 Demo 示範了非同步行為與 Queue 之間的關係。
Event Queue 事件佇列
JavaScript 是 Single Thread(單執行緒)語言,只用一顆 CPU 運轉,一次只能做一件事。但是,計時器(setTimeout)、AJAX、Promise 是屬於 Web APIs,不受單執行緒限制,它們會被放到 Queue 中,等待 JavaScript 執行。
因此,整個流程(Event Loop)就像這樣:
所有要執行的任務會被放到 Call Stack 裡面,JavaScript 的執行緒會逐一執行 Stack 裡的任務,當碰上非同步事件時,為了不讓程式被這些需要等待的事件卡著,就會繼續執行後續的動作。
那這些非同步事件什麼時候才能被執行?答案是,它們會在 Queue 中排隊,等到 JavaScript 的執行緒把 Stack 的任務都消化完了,才輪到 Queue 中的非同步事件依序執行。
觀念重點:非同步事件會被放進排隊序列,先進先出。
單執行緒與 Event Queue 的概念可以參照鐵人賽:一次只能做一件事情的 JavaScript,有動圖更好理解。
單執行緒與 Queue
JavaScript 單執行緒會按照順序執行在 Stack 中的任務。
1 | console.log(a); |
如果在這個例子中,穿插一個非同步事件進去,那麼執行順序會是如何呢?
1 | console.log('a'); |
透過這個例子可以發現,非同步事件會被放到其他行為的後面執行。
我們可以把 setTimeout()
的秒數改成 0,來驗證這個結論是否為真。
1 | console.log('a'); |
秒數改成 0 以後,'c'
依然是最後才印出來,就是因為非同步事件都必須在 Queue 中等待 Stack 裡其他任務結束,才能被執行。
再來看另一個例子,也可以看出在 Queue 中的事件永遠會被放到最後才執行的特徵。
1 | console.log('a'); |
這個例子中,setTimeout()
應該在 3 秒後印出 'b'
,但結果卻是在 5 秒後,排在 'c'
的後面出現,為什麼呢?
這是因為,當 3 秒到時,setTimeout()
就已回來 JavaScript 的執行緒了,但在 Stack 中還有 5 秒 while
迴圈及 console.log('c');
還沒執行完畢。
因此,當迴圈終於跑完時,'c'
接著印出來,而時間早就到了的 'b'
也就緊跟著印出來了。
for 迴圈 - 以 Vue 環境為例
1 | // HTML |
透過 debugger
可以在開發人員工具中看到迴圈的分解動作,我們可以看到 for
迴圈是等所有次數都跑完,數字才渲染出來;而不是像我們所想像的,跑一次迴圈就渲染一個數字在畫面上。
那麼,要如何每跑一次迴圈就渲染一次數字?
答案是,可以用 setTiomeiut()
來達成。
1 | for(let i = 0;i < 5; i++){ |
為什麼用 setTiomeiut()
就能達成呢?
因為 for
迴圈一開始沒用 setTimeout()
時,只有 for
自己一個任務在 Stack 中;而使用 setTimeout()
後,就變成每跑一次迴圈就產生一個存放在 Queue 中的任務,每個 Queue 中的任務又會被依序插入 Stack 中,於是就能一個執行(渲染)完才換成下一個執行(渲染)。
AJAX
傳統網頁在跟後端撈資料時,流程是這樣的:請求 => 回應 => 請求 => 回應;而使用了 AJAX 技術的網頁撈取資料的方法,則是在背景送出請求取得回應。
常見底層
- XMLHttpRequest (可支援 IE 7 以上, jQuery, axios)
- HTML5 Fetch API (IE 11 以下都不支援)
測試用 API
以下兩個網站都可以用來製作測試用的 API,也可以客製化 AJAX 成功後的回傳訊息。
XMLHttpRequest DEMO
非同步 GET 請求
1
2
3
4
5
6var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(){
console.log(this.responseText);
})
xhr.open('GET', url);
xhr.send();AJAX 行為會被丟到 Queue,等取得回應後才回到 JS 執行緒。
同步 GET 請求
1
2
3
4
5
6
7
8
9console.log('start');
var xhr = new XMLHttpRequest();
xhr.open('GET', url, false);
xhr.send();
console.log(xhr.responseText);
console.log('end');
// 'start'
// responseText
// 'end'這邊只是示範同步的 AJAX 是長什麼樣,實務上盡量還是用非同步 AJAX 才能讓網頁效能較好。
非同步 POST 請求
1
2
3
4
5
6
7
8
9// 如何把參數發送到後端
var data = new FormData(); // 宣告 FormData 物件
data.append('id', '5'); // 在 FormData 中塞入資料
var xhr = new XMLHttpRequest();
xhr.addEventListener('load', function(){
console.log(this.responseText);
})
xhr.open('POST', url);
xhr.send(data); // 把帶有資料的 FormData 傳送到後端
Fetch DEMO
Fetch 發送 GET 請求
1
2
3
4
5fetch('url').then(response => {
return response.json(); //解讀 JSON 格式
})
.then(data =>
console.log(data)) // 取得資料Fetch 發送 POST 請求
1
2
3
4
5
6
7
8// 把資料參數傳到後端,一樣要先宣告 FormData
var data = new FormData();
data.append('id', '5');
fetch('url', { method: 'POST', body: data }) // 帶入參數
.then(response => response.json();) // 解讀JSON格式
.then(data =>
console.log(data)) // 取得資料