callback、Promise 和 async/await 簡介
2022-10-06 10:52:20
本篇文章會介紹 javascript 的非同步程式設計發展,從最初的 [Callback](https://developer.mozilla.org/zh-TW/docs/Glossary/Callback_function) 技術,到 2015 年的 [Promise](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise) ([ES6](https://262.ecma-international.org/6.0/)),到 2017年的 [async](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/async_function)/[await](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/await) ([ES8](https://262.ecma-international.org/8.0/))。
如果你還沒有理解非同步程式設計,可以參考[同步](https://developer.mozilla.org/zh-TW/docs/Glossary/Synchronous)、[非同步](https://developer.mozilla.org/zh-TW/docs/Glossary/Asynchronous)和[非同步的 JavaScript 介紹](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Asynchronous/Introducing)
- 一個貫穿全文的例子
- 同步程式設計逛夜市
- Callback 逛夜市
- Promise 逛夜市
- Promise.all 逛夜市
- async/await 逛夜市
- 比較各個方法
- 總結
- 參考資料
## 一個貫穿全文的例子
有一天你去逛夜市,逛夜市一定要**買雞排和珍奶**
``` javascript
class Food {
name = '雞排'
}
class Drink {
name = '珍奶'
}
```
而且**雞排和珍奶都到手你才會開動**,換句話說當你買完雞排你不會先吃掉,一定會等到珍奶也買好。
``` javascript
function eat(food, drink) { // 開動
console.log(food.name, drink.name)
}
```
## 同步程式設計逛夜市
買雞排和買珍奶都要排隊各 2 秒,使用同步等待,也就是**人在現場等候**
``` javascript
function wait(second) {
const ms = second * 1000
const start = new Date()
while (true) {
if (new Date() - start > ms) {
break
}
}
}
```
先去買雞排,再去買珍奶,開動的時候已經等 4 秒
``` javascript
function orderFood() { // 買雞排
wait(2) // 同步等待 2 秒
return new Food()
}
function orderDrink() { // 買珍奶
wait(2) // 同步等待 2 秒
return new Drink()
}
function main() {
const food = orderFood()
const drink = orderDrink()
eat(food, drink)
}
```
## Callback 逛夜市
夜市攤販引進協助後續服務,人不用現場等,攤販備餐完,**攤販呼叫後續事項**
``` javascript
function wait(second, callback, arg) {
const ms = second * 1000
setTimeout(callback, ms, arg)
}
function orderFood(callback) { // 買雞排
wait(2, callback, new Food()) // 非同步等待 2 秒,完成後呼叫後續函式
}
function orderDrink(callback) { // 買珍奶
wait(2, callback, new Drink()) // 非同步等待 2 秒,完成後呼叫後續函式
}
```
而你打算接到電話後,去看看是不是雞排珍奶都完成
``` javascript
let food = undefined
let drink = undefined
function callme() {
if (food !== undefined && drink !== undefined) {
eat(food, drink)
}
}
```
你告訴雞排攤位,做好了放在那邊,並打電話告訴你 (callme)
``` javascript
function setFoodAndCallme(newFood) {
food = newFood
callme()
}
```
你也告訴珍奶攤位一樣的事情
``` javascript
function setDrinkAndCallme(newDrink) {
drink = newDrink
callme()
}
```
Callback 出發逛夜市,由於你點完雞排立刻點珍奶,然後等他們通知,只用 2 秒多一些就拿完雞排珍奶
``` javascript
function main() {
orderFood(setFoodAndCallme)
orderDrink(setDrinkAndCallme)
}
```
## Promise 逛夜市
有一天攤販決定,不想處理後續事項,那件事不該是攤販的工作,所以**餐點完成後讓你自己處理後續事項**
其中 `resolve` 是指非同步處理成功,可以使用這個函式繳交回傳值,例如: `resolve(arg)` 代表回傳 arg
``` javascript
function wait(second, arg) {
const ms = second * 1000
return new Promise((resolve, reject) => setTimeout(() => resolve(arg), ms));
}
function orderFood() { // 買雞排
return wait(2, new Food()) // 非同步等待 2 秒,完成後回傳物件
}
function orderDrink() { // 買珍奶
return wait(2, new Drink()) // 非同步等待 2 秒,完成後回傳物件
}
```
既然攤販不處理後續事項,那就自己處理,**攤販做好餐點告訴你 (`.then()`)**
`.then()` 的參數是一個函式,函式裡是否有參數根據 resolve 決定,上面會帶 arg 進去作為參數,所以就接一個 arg
處理時間和 Callback 大略相同,但是最大不同點是,攤販不需要知道後續事項,交給你自己處理
``` javascript
function main() {
orderFood().then(arg => setFoodAndCallme(arg))
orderDrink().then(arg => setDrinkAndCallme(arg))
}
```
## Promise.all 逛夜市
由於雞排珍奶不同攤販,他們完成的時間不一樣,所以前面才需要 setFood 和 setDrink
今天你希望當**雞排、珍奶都完成時,再通知我去處理**,直接移除 ```setFoodAndCallme```, ```setDrinkAndCallme``` 和 ```callme```
其中 ```[food, drink]``` 採用[解構賦值](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
``` javascript
function main() {
Promise.all([orderFood(), orderDrink()])
.then(([food, drink]) => eat(food, drink))
}
```
## async/await 逛夜市
今天客人希望當**雞排、珍奶都完成時,直接送給客人**,從 Promise.all 小改,就可以在 main 呼叫 eat,而不是 then
main 函式有加 ```async```,Promise 前面有加 ```await```
``` javascript
async function main() {
const [food, drink] = await Promise.all([orderFood(), orderDrink()])
eat(food, drink)
}
```
## 比較各個方法
### wait
``` javascript
/* 同步 */
function wait(second) {
const ms = second * 1000
const start = new Date()
while (true) {
if (new Date() - start > ms) {
break
}
}
}
/* Callback */
function wait(second, callback, arg) {
const ms = second * 1000
setTimeout(callback, ms, arg)
}
/* Promise, Promise.all, async/await */
function wait(second, arg) {
const ms = second * 1000
return new Promise((resolve, reject) => setTimeout(() => resolve(arg), ms));
}
```
### order
``` javascript
/* 同步 */
function orderFood() { // 買雞排
wait(2) // 同步等待 2 秒
return new Food()
}
/* Callback */
function orderFood(callback) { // 買雞排
wait(2, callback, new Food()) // 非同步等待 2 秒,完成後呼叫後續函式
}
/* Promise, Promise.all, async/await */
function orderFood() { // 買雞排
return wait(2, new Food()) // 非同步等待 2 秒,完成後回傳物件
}
```
### set and call
同步和 Promise.all 不需要這個部分,這個方法會讓程式變得複雜,可以的話使用 Promise.all 處理會更好
``` javascript
/* Callback, Promise */
let food = undefined
function setFoodAndCallme(newFood) {
food = newFood
callme()
}
function callme() {
if (food !== undefined && drink !== undefined) {
eat(food, drink)
}
}
```
### main
``` javascript
/* 同步 */
function main() {
const food = orderFood()
const drink = orderDrink()
eat(food, drink)
}
/* Callback */
function main() {
orderFood(setFoodAndCallme)
orderDrink(setDrinkAndCallme)
}
/* Promise */
function main() {
orderFood().then(arg => setFoodAndCallme(arg))
orderDrink().then(arg => setDrinkAndCallme(arg))
}
/* Promise.all */
function main() {
Promise.all([orderFood(), orderDrink()])
.then(([food, drink]) => eat(food, drink))
}
/* async/await */
async function main() {
const [food, drink] = await Promise.all([orderFood(), orderDrink()])
eat(food, drink)
}
```
## 總結
- 同步程式設計就如同人在現場候餐
- 非同步程式設計,**減少等待時間**,**也讓等候時間可以處理其他事情**
- Callback, Promise 和 async/await 是非同步程式設計的技術
- Callback 是將後續事項交給非同步函式,請他完成的時候呼叫 (將後續事項委託攤販)
- Promise 消除非同步函式和 Callback 的耦合 (攤販不需要處理客人後續事項)
- Promise.all 提供整合多個非同步函式的功能 (兩者都完成再通知我)
- async/await 將回傳值取回到呼叫者,讓非同步程式設計概念更接近同步程式設計 (兩者都完成直接傳回給你)
## 參考資料
- [async](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Statements/async_function)
- [await](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/await)
- [Callback](https://developer.mozilla.org/zh-TW/docs/Glossary/Callback_function)
- [ECMAScript® 2015 Language Specification](https://262.ecma-international.org/6.0/)
- [ECMAScript® 2017 Language Specification](https://262.ecma-international.org/8.0/)
- [Promise](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Promise)
- [同步](https://developer.mozilla.org/zh-TW/docs/Glossary/Synchronous)
- [非同步](https://developer.mozilla.org/zh-TW/docs/Glossary/Asynchronous)
- [非同步的 JavaScript 介紹](https://developer.mozilla.org/zh-TW/docs/Learn/JavaScript/Asynchronous/Introducing)
- [解構賦值](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)
點擊複製文章連結
X