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