從 ES6 Promise 到 ES7 的 async await 入門

francois-bg
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 掛上去,你就進入未來了~~

延伸閱讀

4 comments on “從 ES6 Promise 到 ES7 的 async await 入門

  1. 而如本文中有加async不會報錯是因為本例async function回傳的Promise中有Promise剛好符合await的形式。
    但是實際上是多包了一層Promise

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *