
JavaScript 的生態自從 node 出現以後,一切都變了樣,非同步變成更需要用心處理的事,這幾年從 ES6 把 Promise 標準化,發展到了 ES7 的 async/await。那在現在甚至未來幾年,在遇到非同步的問題時,最佳解會是什麼呢?
讓我們從 Promise 說起吧…先複習一下
概念
從實務面來看,Promise 能解決日常的哪幾種問題
- 循序執行
- 並發執行
- 錯誤處理
講太多理論好頭痛,直接看幾個實例~
循序執行
解決 callback 地獄不用多講了,直接把 promise 串起來,就能夠讓波動拳消失
//callbacks
getUser(function() {
getArticle(function() {
success();
});
});
//promises
getUser().then(getArticle).then(success);
併發執行
第二個主要解決的問題是在並發執行多個任務的時候,可以有一個地方回收結果,這在沒有任何工具的情況很難做的簡單又漂亮。理論上解法是用一個 count 去算工作有幾個已經完成了,count++ 以後如果等於 total 就能 callback 了。
Promise.all([getUser, getArticle]).then(success);
錯誤處理
第三個是錯誤處理,就是統一用 catch 來處理這個 promise chain 裡面丟出現的錯誤,在 callback 裡面無法用一個 try 接到深層的 error。
getUser().then(getArticle).then(success).catch(error);
實務案例
另外想談一個實務上會遇到的情況,並且在接下來會用此例子來探討 ES7 的一些作法。
如果我有一個 promise 的 array 叫做 tasks 想要循序執行,像是 async.js 的 series,在 promise 標準裡面沒有 API,但是比較精練的寫法是
var p = Promise.resolve();
tasks.reduce(function(p, fn) {
return p = p.then(fn);
}, p).then(success);
Async/Await
那在 async / await 的世界都是怎麼做這件事呢?先看一個小範例,了解 async / await 的運作,demo
async function wait(data) {
return new Promise((resolve, reject) => {
setTimeout(v => resolve(data), 1000);
})
}
(async function() {
let res = await wait('test');
console.log(res);
})();
console.log('start')
//output
/*
start
test
*/
基本上這個語法的目的就是讓程式能再某些想卡住的地方能卡住,但是不是真的卡住,只是種語法糖。
而這個地方就是 async function 內,你可以用 await 的語法去呼叫另外一個 async function,而且一定要在 async function 才能這樣做,這樣才能確保你不會把主線程給卡住。
所以進入 async function 裡面隨時有可能發起非同步,而實際上這種非同步事件可以由 promise or generator 發起。如果有這種事件的函式,就可以用 await 把 async function 裡面的工作暫停,先回主線程往下執行,直到 await 結束才回來。
大致上的原理是這樣,所以要解決上述的循序程式就變得很直覺的像同步的寫法一樣demo
async function wait(i) {
return new Promise((resolve, reject) => {
setTimeout(v => resolve(i), 1000);
})
}
(async function() {
for(var i in [1, 2, 3, 4, 5]) {
let res = await wait(i);
console.log(res);
}
})();
另外貼心的一點是 async function 預設會回傳一個 promise,就像 promise api 裡面 then 會幫你做的事一樣,只是搭配 await 以後其實就可以在非同步工作中用 await 語法取代掉 then 讓語法更簡潔。
//async with then
(async function() {
let res = await wait('wait 1 sec');
console.log(res);
return res;
})().then(async function() {
let res = await wait('wait 2 sec', 2000);
console.log(res);
return res;
})
//pure await
(async function() {
var res = await wait('wait 1 sec');
console.log(res);
res = await wait('wait 2 sec', 2000);
console.log(res);
})()
小結
雖然現在距離 ES7 還有點距離,但是 babel 都已經實現了,而且很多前端都已經開始大量採用 babel 來編 React、jsx、ES6,所以下次在遇到非同步問題時,已經可以考慮這個作法了。
只是在前端還是要考慮現在編出來的程式會有點肥 -_-,在後端就比較沒有這個問題了, babe-register 掛上去,你就進入未來了~~
延伸閱讀
- ES 5-6-7: From Callbacks to Promises to Generators to Async/await
- ES7 async function
- ES7 async functions – a step in the wrong direction
喵喵好棒~~~~~~~~~
喵喵好棒~~~~~~~~~
請問你哪位@Q@?
而如本文中有加async不會報錯是因為本例async function回傳的Promise中有Promise剛好符合await的形式。
但是實際上是多包了一層Promise