2023-11-13|閱讀時間 ‧ 約 7 分鐘

JS 筆記 | callback, promise, async/await 的前世今生

非同步的概念在 JavaScript 中一直是一大命題,通常用來執行那些可能很耗時的工作,比如打 API。所以所謂非同步用的好,網頁順暢沒煩惱。

而 JavaScript 中執行非同步的方式從最早的 callback function 已經推進到 promise,再到現在簡潔的 async/await,不只實務上常用,也已經變成幾乎必考的面試或技術題。

Callback Function

常會看到文章寫到 callback 都會提到 callback 就是 “把一個 B 函式做為參數傳進另一個 A 函式,透過 A 函式來呼叫它”。

這樣做的原因有兩個:

  1. 讓 B 函式滿足某個條件才 “被動地” 去執行。
  2. 讓函式之間有執行的順序。

白話一點就是我要做完 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)



Promise

試想一下,剛剛的例子僅是 A 執行完去執行 B,但如果今天 B 執行完還要執行 C、D、E…呢?恐怕一不小心就迷失在茫茫的 callback 大海中了。

萬幸,promise 的從天而降讓我們不需要一層包一層的程式碼,可以更容易看到函式間的次序。

Promise 的使用上會先建立一個 promise 物件,而 promise 通常會有三種狀態:

  1. Pending:表示正在處理中。
  2. Resolve:表示處理成功。
  3. Reject:表示處理失敗。

我們通常靠著 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))



Async/await

這是基於 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 不用 promise

在會議上前輩問了一個問題:為什麼現在推崇使用 async/await?

我說:因為可以把非同步用同步的語法寫出...?

很顯然,我了解得不夠透徹,所以開始了同步與非同步小課堂~

大家都知道 JavaScript 的程式碼執行是一行接著一行這樣執行下來的嘛,所謂單執行序語言,遇到非同步的事件時會變成多執行序,比方說 setTimeout 被丟到 web API 中執行,promiseasync/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 請求已經結束了。

這是今天了解到得額外知識。



參考資料

  1. callback, promise, async/await 使用方式教學以及介紹 Part I
  2. 我要學會 JS(三):callback、Promise 和 async/await 那些事兒
分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.