從 ES6 Promise 到 ES7 的 async await 入門

francois-bg
JavaScript 的生態自從 node 出現以後,一切都變了樣,非同步變成更需要用心處理的事,這幾年從 ES6 把 Promise 標準化,發展到了 ES7 的 async/await。那在現在甚至未來幾年,在遇到非同步的問題時,最佳解會是什麼呢?

讓我們從 Promise 說起吧…先複習一下

概念

從實務面來看,Promise 能解決日常的哪幾種問題

  • 循序執行
  • 並發執行
  • 錯誤處理

講太多理論好頭痛,直接看幾個實例~

循序執行

解決 callback 地獄不用多講了,直接把 promise 串起來,就能夠讓波動拳消失

1
2
3
4
5
6
7
8
9
//callbacks
getUser(function() {
   getArticle(function() {
      success();
   });
});
 
//promises
getUser().then(getArticle).then(success);

併發執行

第二個主要解決的問題是在並發執行多個任務的時候,可以有一個地方回收結果,這在沒有任何工具的情況很難做的簡單又漂亮。理論上解法是用一個 count 去算工作有幾個已經完成了,count++ 以後如果等於 total 就能 callback 了。

1
Promise.all([getUser, getArticle]).then(success);

錯誤處理

第三個是錯誤處理,就是統一用 catch 來處理這個 promise chain 裡面丟出現的錯誤,在 callback 裡面無法用一個 try 接到深層的 error。

1
getUser().then(getArticle).then(success).catch(error);

實務案例

另外想談一個實務上會遇到的情況,並且在接下來會用此例子來探討 ES7 的一些作法。

如果我有一個 promise 的 array 叫做 tasks 想要循序執行,像是 async.js 的 series,在 promise 標準裡面沒有 API,但是比較精練的寫法是

1
2
3
4
var p = Promise.resolve();
tasks.reduce(function(p, fn) {
  return p = p.then(fn);
}, p).then(success);

Async/Await

那在 async / await 的世界都是怎麼做這件事呢?先看一個小範例,了解 async / await 的運作,demo

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
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

01
02
03
04
05
06
07
08
09
10
11
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 讓語法更簡潔。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
//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

發佈回覆給「cisan」的留言 取消回覆

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