本篇文章會介紹 javascript 的非同步程式設計發展,從最初的 Callback 技術,到 2015 年的 Promise (ES6),到 2017年的 async/await (ES8)。
如果你還沒有理解非同步程式設計,可以參考同步、非同步和非同步的 JavaScript 介紹
- 一個貫穿全文的例子
- 同步程式設計逛夜市
- Callback 逛夜市
- Promise 逛夜市
- Promise.all 逛夜市
- async/await 逛夜市
- 比較各個方法
- 總結
- 參考資料
一個貫穿全文的例子
有一天你去逛夜市,逛夜市一定要買雞排和珍奶
1 2 3 4 5 6 7
| class Food { name = '雞排' }
class Drink { name = '珍奶' }
|
而且雞排和珍奶都到手你才會開動,換句話說當你買完雞排你不會先吃掉,一定會等到珍奶也買好。
1 2 3
| function eat(food, drink) { console.log(food.name, drink.name) }
|
同步程式設計逛夜市 (code)
買雞排和買珍奶都要排隊各 2 秒,使用同步等待,也就是人在現場等候
1 2 3 4 5 6 7 8 9
| function wait(second) { const ms = second * 1000 const start = new Date() while (true) { if (new Date() - start > ms) { break } } }
|
先去買雞排,再去買珍奶,開動的時候已經等 4 秒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function orderFood() { wait(2) return new Food() }
function orderDrink() { wait(2) return new Drink() }
function main() { const food = orderFood() const drink = orderDrink() eat(food, drink) }
|
Callback 逛夜市 (code)
夜市攤販引進協助後續服務,人不用現場等,攤販備餐完,攤販呼叫後續事項
1 2 3 4 5 6 7 8 9 10 11 12
| function wait(second, callback, arg) { const ms = second * 1000 setTimeout(callback, ms, arg) }
function orderFood(callback) { wait(2, callback, new Food()) }
function orderDrink(callback) { wait(2, callback, new Drink()) }
|
而你打算接到電話後,去看看是不是雞排珍奶都完成
1 2 3 4 5 6 7 8
| let food = undefined let drink = undefined
function callme() { if (food !== undefined && drink !== undefined) { eat(food, drink) } }
|
你告訴雞排攤位,做好了放在那邊,並打電話告訴你 (callme)
1 2 3 4
| function setFoodAndCallme(newFood) { food = newFood callme() }
|
你也告訴珍奶攤位一樣的事情
1 2 3 4
| function setDrinkAndCallme(newDrink) { drink = newDrink callme() }
|
Callback 出發逛夜市,由於你點完雞排立刻點珍奶,然後等他們通知,只用 2 秒多一些就拿完雞排珍奶
1 2 3 4
| function main() { orderFood(setFoodAndCallme) orderDrink(setDrinkAndCallme) }
|
Promise 逛夜市 (code)
有一天攤販決定,不想處理後續事項,那件事不該是攤販的工作,所以餐點完成後讓你自己處理後續事項
其中 resolve
是指非同步處理成功,可以使用這個函式繳交回傳值,例如: resolve(arg)
代表回傳 arg
1 2 3 4 5 6 7 8 9 10 11 12
| function wait(second, arg) { const ms = second * 1000 return new Promise((resolve, reject) => setTimeout(() => resolve(arg), ms)); }
function orderFood() { return wait(2, new Food()) }
function orderDrink() { return wait(2, new Drink()) }
|
既然攤販不處理後續事項,那就自己處理,攤販做好餐點告訴你 (.then()
)
.then()
的參數是一個函式,函式裡是否有參數根據 resolve 決定,上面會帶 arg 進去作為參數,所以就接一個 arg
處理時間和 Callback 大略相同,但是最大不同點是,攤販不需要知道後續事項,交給你自己處理
1 2 3 4
| function main() { orderFood().then(arg => setFoodAndCallme(arg)) orderDrink().then(arg => setDrinkAndCallme(arg)) }
|
Promise.all 逛夜市 (code)
由於雞排珍奶不同攤販,他們完成的時間不一樣,所以前面才需要 setFood 和 setDrink
今天你希望當雞排、珍奶都完成時,再通知我去處理,直接移除 setFoodAndCallme
, setDrinkAndCallme
和 callme
其中 [food, drink]
採用解構賦值
1 2 3 4
| function main() { Promise.all([orderFood(), orderDrink()]) .then(([food, drink]) => eat(food, drink)) }
|
async/await 逛夜市 (code)
今天客人希望當雞排、珍奶都完成時,直接送給客人,從 Promise.all 小改,就可以在 main 呼叫 eat,而不是 then
main 函式有加 async
,Promise 前面有加 await
1 2 3 4
| async function main() { const [food, drink] = await Promise.all([orderFood(), orderDrink()]) eat(food, drink) }
|
比較各個方法
wait
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| function wait(second) { const ms = second * 1000 const start = new Date() while (true) { if (new Date() - start > ms) { break } } }
function wait(second, callback, arg) { const ms = second * 1000 setTimeout(callback, ms, arg) }
function wait(second, arg) { const ms = second * 1000 return new Promise((resolve, reject) => setTimeout(() => resolve(arg), ms)); }
|
order
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function orderFood() { wait(2) return new Food() }
function orderFood(callback) { wait(2, callback, new Food()) }
function orderFood() { return wait(2, new Food()) }
|
set and call
同步和 Promise.all 不需要這個部分,這個方法會讓程式變得複雜,可以的話使用 Promise.all 處理會更好
1 2 3 4 5 6 7 8 9 10 11 12 13
| let food = undefined
function setFoodAndCallme(newFood) { food = newFood callme() }
function callme() { if (food !== undefined && drink !== undefined) { eat(food, drink) } }
|
main
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| function main() { const food = orderFood() const drink = orderDrink() eat(food, drink) }
function main() { orderFood(setFoodAndCallme) orderDrink(setDrinkAndCallme) }
function main() { orderFood().then(arg => setFoodAndCallme(arg)) orderDrink().then(arg => setDrinkAndCallme(arg)) }
function main() { Promise.all([orderFood(), orderDrink()]) .then(([food, drink]) => eat(food, drink)) }
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 將回傳值取回到呼叫者,讓非同步程式設計概念更接近同步程式設計 (兩者都完成直接傳回給你)
參考資料