[JS筆記]使閱讀更直覺的Async / await

Kim.H
6 min readNov 24, 2023

--

之前介紹過Javascript用來處理非同步的callback和promise,本篇是第三部曲,主角是使閱讀更直覺的async / await。

非同步處理三部曲:

  1. 何謂Callback function?
  2. 避免Callback Hell的Promise
  3. 使閱讀更直覺的Async / await

若要用比喻來形容,callback是非同步處理的老大哥,像個寶藏圖藏在好幾層寶箱的最深處,你需要不斷地挖呀挖呀挖,逐層拆解寶箱才能夠得到它。而Promise則像說一段故事,裏頭有許多「然後」,不斷地把故事結局傳遞給下一集,產生新的故事。雖然比起巢狀的Callback,Promise透過鏈接(.then/.catch)的方式讓程式碼能由上而下地閱讀,看起來更直覺更好維護了,但和我們最初開始學程式時,所理解的「逐行執行」還是不太一樣,對吧?本篇主角async / await的出現,讓非同步語法能夠以同步的方式呈現,使閱讀更加容易,是一種「語法糖Syntactic sugar」。

還記得我們在new Promise物件時,最後會用resolve或reject將結果回傳嗎?執行promise之後,可以用.then來取得回傳值,或是.catch來捕捉錯誤。

//自定義一個promise物件
function promise(value) {
return new Promise((resolve, reject) => {
if(value){
resolve('成功')
}else {
reject('失敗')
}
})
}

promise(1)
.then(result => {
console.log(result)
})
.catch(e => {
console.log(e)
})

雖然promise的出現使非同步處理比callback巢狀的方式更好閱讀了,但和「同步」處理的程式碼還是有點不太一樣。而async/await以promise為基礎,使程式碼撰寫方式能和一般同步處理沒有太差異,在更複雜的流程處理時,讓程式碼更清楚更好閱讀。

使用方式很簡單,在function前加上 async ,在promise前加上 await ,等待程式碼執行完畢,就能將promise執行後resolve的值,賦值給你宣告的一個變數。簡單加上兩個關鍵字 async & await,就可以將程式碼以同步的方式來呈現,是不是就像我們平常寫的程式碼,更好閱讀了呢?我們一樣用上面自定義的promise來示範,首先來看一個resolve的例子:

async function test() { 
const result = await promise(1)
console.log(result)
}

test() // 成功

那如果我們將promise(1)改成promise(0),故意讓promise進入到reject的狀態呢?

(node:6060) UnhandledPromiseRejectionWarning: 失敗
(Use `node --trace-warnings ...` to show where the warning was created)
(node:6060) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:6060) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

馬上就出現錯誤了!原因是雖然async/await可以承接resolve,但他沒有辦法處理promise reject出來的錯誤,所以我們需要用try & catch 來將非同步的程式碼包住,讓錯誤能被捕捉到:

async function test() { 
try{
const result = await promise()
console.log(result)
} catch(e){
console.log(e)
}
}

test() // 失敗

下面這個例子刻意放了兩個會出現失敗的promise,但只出現一次失敗,為什麼呢?

async function test() { 
try{
const result = await promise()
const r2 = await promise()
console.log(result, r2)
} catch(e){
console.log(e)
}
}

test() // 失敗

因為try…catch…無法像.then().catch(),能將回傳值用鏈接的方式傳遞給下一層,若要單獨處理每項reject,就必須分別用try…catch…包覆住:

async function test() { 
try{
const result = await promise()
console.log(result)
} catch(e){
console.log(e)
}
try{
const r2 = await promise()
console.log(r2)
} catch(e){
console.log(e)
}
}

test() // 失敗, 失敗

async/await還有其他缺點,其中一個是無法使用promise的原生語法,例如race()。另外一個缺點就是會阻塞程式碼,需要「等待」前面的程式碼執行完成,才能往後執行,無法做到併行處理,可能導致整體效能變差。

終於把非同步處理三部曲完成了,剛接觸的時候覺得async / await 很好閱讀、方便撰寫,有一陣子碰到非同步處理都是選擇這個方式來撰寫,後來真的進到職場實務處理時,看見前輩都使用promise,好奇之下詢問了原因,才發現這兩種方式有各自的優缺點,至於要選擇哪一種,就還是得看開發的狀況來決定了。

非同步處理三部曲:

  1. 何謂Callback function?
  2. 避免Callback Hell的Promise
  3. 使閱讀更直覺的Async / await

--

--

Kim.H

現任菜鳥後端工程師 / 2022.12 正式踏入轉職之旅 - 2023.09轉職成功