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

閱讀時間約 6 分鐘

非同步的概念在 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 那些事兒
avatar-img
18會員
37內容數
這個專題用來存放我在學習網頁開發時的心得及知識。
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Jeremy Ho的沙龍 的其他內容
JavaScript Object 基礎操作筆記
JavaScript Array 基本操作筆記
JavaScript event loop / asynchronous.
JavaScript Object 基礎操作筆記
JavaScript Array 基本操作筆記
JavaScript event loop / asynchronous.
你可能也想看
Google News 追蹤
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
簡要說明 JavaScript 的 Event Loop JavaScript 是單執行緒 (single-threaded) 語言,這意味著它一次只能執行一件事,因此所有函式都需要排隊等待執行,這被稱為同步 (synchronous)。在同步操作中,若函式過多或過於複雜,會導致程式阻塞 (blo
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
認識 async/await基本概念: async 的本質是 promise 的語法糖 ,只要 function 標記為 async,就表示裡頭可以撰寫 await 的同步語法,而 await 顧名思義就是「等待」,它會確保一個 promise 物件都解決 ( resolve ) 或出錯 ( re
什麼是 Promise.all? 在有多個 Promise 的時候,使用 Promise.all 可以確保「所有的 Promise 都執行完以後,才進入 then」。 Promise.all 語法結構: Promise.all 接受的參數是陣列形式。 什麼時候要使用 Promise.all?
※ Promise基本介紹 什麼是 Promise? Promise 是 JavaScript 的一個構造函式,用於創建表示非同步操作的物件實例。使用 new Promise() 時,你會創建一個包含非同步操作的實例,這個實例可以透過其繼承的方法如 then(), catch(), 和 fina
※ 同步概念: 單純地「由上而下」執行程式碼,而且一次只執行一件事,也就是「按順序執行,一個動作結束才能切換到下一個」。缺點是你需要「等待」事情執行完畢,才能繼續往下走。 ※ 非同步概念: 盡可能讓主要的執行程序不需要停下來等待,若遇到要等待的事情,就發起一個「非同步處理」,讓主程序繼續執行,
※ 函式基礎介紹: ※ JavaScript 特殊的函式特性: 函式可以當成值來傳遞 (可以放進變數或放進物件) 函式可以當成函式的參數 callback - 在特定事件中觸發函式 (非同步特性) ※ 函式的基本寫法: ※ 調用 (invoke) 函式: "調用" 意指呼叫或執行
Thumbnail
立即(調用)函式 (簡稱 IIFE,Immediately Invoked Function Expression) 是種在定義完可以馬上執行的函式表達式。
Thumbnail
非同步程式設計(Asynchronous programming) 或是簡單的稱之為 async,它是一種並發程式模型(concurrent programming model),其目的就是讓多個任務能同時在作業系統的執行緒上執行,並透過 async/.await 保留同步。
Thumbnail
分享一個有趣的套件,名為 await-to-js。 可以讓 Promise 與 await 的寫法更簡潔。
Thumbnail
簡要說明 JavaScript 的 Event Loop JavaScript 是單執行緒 (single-threaded) 語言,這意味著它一次只能執行一件事,因此所有函式都需要排隊等待執行,這被稱為同步 (synchronous)。在同步操作中,若函式過多或過於複雜,會導致程式阻塞 (blo
※ 非同步概念總複習 為什麼要使用 Promise? 在 JavaScript 開發中,處理非同步操作是常見需求,涉及如文件讀寫、數據庫查詢或網路請求等耗時任務。傳統的回調方式可能導致代碼結構混亂,稱為「回調地獄」,難以維護和理解。 Promise 是解決這問題的方法。它是一個物件(objec
認識 async/await基本概念: async 的本質是 promise 的語法糖 ,只要 function 標記為 async,就表示裡頭可以撰寫 await 的同步語法,而 await 顧名思義就是「等待」,它會確保一個 promise 物件都解決 ( resolve ) 或出錯 ( re
什麼是 Promise.all? 在有多個 Promise 的時候,使用 Promise.all 可以確保「所有的 Promise 都執行完以後,才進入 then」。 Promise.all 語法結構: Promise.all 接受的參數是陣列形式。 什麼時候要使用 Promise.all?
※ Promise基本介紹 什麼是 Promise? Promise 是 JavaScript 的一個構造函式,用於創建表示非同步操作的物件實例。使用 new Promise() 時,你會創建一個包含非同步操作的實例,這個實例可以透過其繼承的方法如 then(), catch(), 和 fina
※ 同步概念: 單純地「由上而下」執行程式碼,而且一次只執行一件事,也就是「按順序執行,一個動作結束才能切換到下一個」。缺點是你需要「等待」事情執行完畢,才能繼續往下走。 ※ 非同步概念: 盡可能讓主要的執行程序不需要停下來等待,若遇到要等待的事情,就發起一個「非同步處理」,讓主程序繼續執行,
※ 函式基礎介紹: ※ JavaScript 特殊的函式特性: 函式可以當成值來傳遞 (可以放進變數或放進物件) 函式可以當成函式的參數 callback - 在特定事件中觸發函式 (非同步特性) ※ 函式的基本寫法: ※ 調用 (invoke) 函式: "調用" 意指呼叫或執行
Thumbnail
立即(調用)函式 (簡稱 IIFE,Immediately Invoked Function Expression) 是種在定義完可以馬上執行的函式表達式。
Thumbnail
非同步程式設計(Asynchronous programming) 或是簡單的稱之為 async,它是一種並發程式模型(concurrent programming model),其目的就是讓多個任務能同時在作業系統的執行緒上執行,並透過 async/.await 保留同步。