真神奇。
簡述
首先以下都只是我個人實驗出的結果,並根據實驗結果來得出的「結論」。沒有參考規範,也毫無明確性可言(也許只是我唬爛)。所以當作參考就好,我的觀點很有可能是錯的。
範例
先來看一般的同步處理,瀏覽器在處理同步段落的時候會出現「freeze(凍結)」的情境,必須等到同步執行完後才「unfreeze(解凍)」:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ~async function() { document .querySelector('button') .addEventListener('click', () => { console.log('click') })
for (let i=0; i<=10000000000; i++) {
}
console.log('loop finished') }()
|
Output:
關於同步行為在 Event loop 的機制可以參考這張精美的圖:
接下來是 await
等待 Promise
執行完後的處理。
我原本預期會跟上面一樣,因為 await
不就是把非同步變成同步嗎?它必須等到 resolve
後才執行後面的程式碼,但實驗結果是出乎預料的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| ~async function() { document .querySelector('button') .addEventListener('click', () => { console.log('click') }) await new Promise((resolve, reject) => { setTimeout(() => { console.log('finished') resolve() }, 1000 * 5) }) console.log('yo') }()
|
Output:
結論
其實我沒有很懂背後的原理是什麼,不過從結果來看似乎 await
並不是真的把 Promise
從「非同步」變成「同步」,而是有某種機制可以讓 resolve
執行完後才接著跑,而最重要的是這個機制不會讓瀏覽器被凍結住。
題外話,會無聊做這實驗是因為學了 await
後突然回想起同步的概念。同步的特性是會讓瀏覽器「卡在哪裡等」。所以推測如果真是如此的話,那不就很有可能在等某個 Promise 結束前都被「卡住」嗎?這樣不會有問題嗎?因此就抱著好奇的心來測試看,做了這個小實驗。
最後不得不說設計出這些東西的人真的很強大。
後記
(原本的推導過程也蠻有趣的,所以就留著不修正了)
後來上網查之後理解了,原來 await
只是把 Promise
包裝起來的語法糖。意思是說 await
只是讓你「看起來好像是同步」,但背後其實是幫你把東西放到 .then()
裡面一個一個執行,參考下面的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| ~async function() { document .querySelector('button') .addEventListener('click', () => { console.log('click') }) await waitFiveSeconds() console.log('yo')
}()
function waitFiveSeconds() { return new Promise((resolve, reject) => { setTimeout(function() { resolve() }, 1000 * 5) }) }
|
但把糖果拆開後就發現一樣是 .then
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| ~async function() { document .querySelector('button') .addEventListener('click', () => { console.log('click') })
waitFiveSeconds() .then(() => undefined) .then(() => console.log('yo'))
}()
function waitFiveSeconds() { return new Promise((resolve, reject) => { setTimeout(function() { resolve() }, 1000 * 5) }) }
|
所以這也是為什麼 await
一定要放在 async
函式裡面,因為在裡面它才能夠把「非同步」用 Promise 包裝起來,讓你感覺像是「同步」執行一樣,但其實背後還是用 .then
一行一行來執行的。
最後在附上一個小範例,看你能不能猜出正確的執行順序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| ~async function() { console.log('sync') document .querySelector('button') .addEventListener('click', () => { console.log('click') }) await new Promise((resolve, reject) => { setTimeout(() => { console.log('finished') resolve() }, 1000 * 5) }) console.log('yo') }()
console.log('haha')
|
答案是: