非同步的概念在 JavaScript 中一直是一大命題,通常用來執行那些可能很耗時的工作,比如打 API。所以所謂非同步用的好,網頁順暢沒煩惱。
而 JavaScript 中執行非同步的方式從最早的 callback function
已經推進到 promise
,再到現在簡潔的 async/await
,不只實務上常用,也已經變成幾乎必考的面試或技術題。
常會看到文章寫到 callback
都會提到 callback
就是 “把一個 B 函式做為參數傳進另一個 A 函式,透過 A 函式來呼叫它”。
這樣做的原因有兩個:
白話一點就是我要做完 A 才去執行 B。所以舉個例子,我要數字相加的結果如果有大於 5 (A函式) 時,去對相加後的結果乘以 10 (B函式),可以這樣寫:
const addNum = (a, b, callback) =>{
const plusNum = a + b
if(plusNum > 5){
callback(plusNum)
}else{
console.log('the plusNum < 5')
}
}
const multiNum = (num) => console.log(num * 10)
addNum(6, 2, multiNum)
試想一下,剛剛的例子僅是 A 執行完去執行 B,但如果今天 B 執行完還要執行 C、D、E…呢?恐怕一不小心就迷失在茫茫的 callback
大海中了。
萬幸,promise
的從天而降讓我們不需要一層包一層的程式碼,可以更容易看到函式間的次序。
Promise
的使用上會先建立一個 promise
物件,而 promise
通常會有三種狀態:
我們通常靠著 then
來接收成果的結果並繼續處理下一件事,然後用 catch
來捕獲失敗的狀況。
所以 promise
的語法架構大概是像這樣:A().then(B()).catch(err)
。
把前一段的程式碼改成 promise
會像這樣:
const addNum = (a, b)=>{
return new Promise((resolve, reject)=>{
const plusNum = a + b
if(plusNum > 5){
resolve(plusNum)
}else{
reject('reject!')
}
})
}
addNum(6, 2)
.then(num => multiNum(num))
.catch(err => console.error(err))
這是基於 promise
的語法糖,他一切的運作原理都還是建立在 promise
上,只是 async/await
的寫法讓我們可以用看似同步的語法來撰寫非同步的內容。
我們會再函式一開始用 async
定義一個非同步函式,並在內部使用 await
等待 promise
執行完畢 (A函式),然後再去執行下一個動作 (B函式)。
沒錯,我們還是需要用到 promise
,就像一開始講的,async/await
只是 promise
的語法糖,讓我們不用 then 下去。
所以如果要把前述的 promise
寫法改成 async/await
,我們還是得保留 promise
的建立。
而在 async/await
中,我們依靠 try
來處理 promise
操作成功的情況,然後靠 catch
捕獲錯誤結果。
const calculate = async(a, b) =>{
try{
const plusNum = await addNum(a, b)
multiNum(plusNum)
}
catch(err){
console.error(err)
}
}
calculate(6, 2)
會有一個問題,就是 B 函式multiNum(plusNum)
有沒有需要加上 await
?答案是加了可以動,但沒必要加。
像前述講的,await
在等待的是一個 promise
非同步函式的操作完成,但我們的multiNum(plusNum)
實際是個同步函式,為了不必要的誤會 (不論是人的誤會還是程式碼執行的誤會),盡量不要在同步函式前添加 await
。
在會議上前輩問了一個問題:為什麼現在推崇使用 async/await?
我說:因為可以把非同步用同步的語法寫出...?
很顯然,我了解得不夠透徹,所以開始了同步與非同步小課堂~
大家都知道 JavaScript 的程式碼執行是一行接著一行這樣執行下來的嘛,所謂單執行序語言,遇到非同步的事件時會變成多執行序,比方說 setTimeout
被丟到 web API 中執行,promise
跟 async/await
的差異就在這裡。
當我們的程式碼執行到 promise
時,其實是進入多執行序的,ok,這滿合理的,因為 promise
是個非同步事件。但這樣有個問題,假設今天 promise
是打一組 API 做資料請求,在他後面有一串 code,比如是點擊按鈕渲染取得的資料內容,我們勢必得寫一組 code 去手動監聽剛剛的 API 請求到底結束沒、有沒有資料回來。
但 async/await
不一樣,當今天整段 code 運行中遇到 async
function,在 async
function 後面的 code 會整組停下來,等待 async
function 中 await
的非同步事件處理完成。對應剛剛的情況,當我們使用 async/await
做 API 請求,我們是不用手動監聽的,因為當 async
function 後面的程式碼能開始執行時,就表示 API 請求已經結束了。
這是今天了解到得額外知識。