iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 22
0
Modern Web

前端常見問題攻略系列 第 22

JS async/await 系列:延伸運用篇

使用 try...catch 進行錯誤的處理

前面幾段都僅有提到 resolve 的結果,但實際上程式碼的運行不會都只有成功,在 Promise 定義也就包含了 resolvereject 的回傳,而調用 Promise 的方法時也就可以使用 thencatch 來接收成功及失敗的結果。

https://ithelp.ithome.com.tw/upload/images/20201007/20083608bxMZ7P1Rnr.png

thencatch 範例程式碼,如果運行正確則會繼續往下運行,當遭遇失敗則會跳到 catch 的流程。

promiseFn(1)
  .then(res => {
    console.log(res);
    return promiseFn(0);
  })
  .then(res => {
    console.log(res)
  })
  .catch(err => {
    console.log(err)
  });

https://ithelp.ithome.com.tw/upload/images/20201007/20083608mv0uTEY5EQ.png

相對來說 Promise 的成功、失敗流程的混合撰寫,容易在維護上難以除錯。

**重要:**當使用 async/await 時,因為該片段程式碼已轉為同步的形式,如果遇到錯誤沒有進行例外處理,則會造成後續的程式碼無法繼續運行。

async function getData2() {
  const data1 = await promiseFn(1);
  const data2 = await promiseFn(0);
  // Uncaught (in promise) 失敗
  console.log(data1, data2);
}
getData2();

此段程式碼會直接中止,不會出現 console.log 的結果。

async/await 錯誤流程可以使用 try...catch 陳述式管理流程,將原有的程式碼直接置入於 try 流程內,當遇到例外的錯誤時則撰寫在 catch 區塊內。

以下範例來說 try 內的程式碼與原本介紹的一般流程是相同的,僅不過加入了 catch 來處理 reject 的錯誤流程。這樣的結構下,就可以將程式碼區分為成功、錯誤兩個流程,閱讀上也會更加容易。

async function getData2() {
  try { // 專注在成功
    const data1 = await promiseFn(1);
    const data2 = await promiseFn(0);
    console.log(data1, data2);
  }
  catch (err) { // 專注在錯誤
    console.log('catch', err);
  }
}
getData2();

圖解概念:正常流程與錯誤流程分離,且一般流程中還可維持同步執行的特色,避免巢狀的程式碼。
https://ithelp.ithome.com.tw/upload/images/20201007/20083608hrQEeJwr2S.png

執行順序的技巧

Promise 是透過鏈接及 Promise 的方法(Promise.all, Promise.race)來達到不同的執行順序方法。async/await 則讓非同步有了同步的寫法,因此許多原有的 JS 語法都可以一起搭配做使用,如迴圈、判斷式都是可利用的技巧,在了解這段以前,需要先知道非同步主要有兩個時間點:

  1. 送出 “請求” 的時間
  2. 取得 “回應” 的時間

依據這段概念,又可以區分成:

  • 請求依序發出:一個一個往下執行
  • 請求平行發出:全部的請求一起發出
  • 回應依序列出:依據請求發出的順序,依序列出資源
  • 回應統一列出:不管什麼時候取得都統一列出

https://ithelp.ithome.com.tw/upload/images/20201007/20083608LMuabUdkuo.png

請求依序發出時:下一個請求必須等待前一個取得回應後才能繼續動作

https://ithelp.ithome.com.tw/upload/images/20201007/20083608WMHEgyQeou.png

請求為平行發出時:無論先後順序都將同時發出請求。

Promise.all 平行執行,統一列出回應資訊

Promise.all() 是 Promise 物件下的方法,此方法可傳入一個陣列,陣列包含多個 promise,Promise.all()可以統一發出所有陣列內的請求,並取得所有回應時統一回傳,async/await 可搭配此語法得到一次性的所有回應。

async function promiseAll() {
  const data = await Promise.all([promiseFn(1, 3000), promiseFn(2)]);
  console.log(data)
}
promiseAll(); // ["1, 成功", "2, 成功"]

https://ithelp.ithome.com.tw/upload/images/20201007/200836086MlDlL165g.png

取得結果與送出的陣列順序相同。

依序發出請求的方式

for...loop 是可以被中斷的迴圈形式(使用 break 中斷),所以其實可以理解他是屬於依序執行的陳述式,當 for 內包含 await 時就必須等待 await 取得回應後才能執行下一個迴圈。

本範例中先建立一個陣列 arrayData,其中包含數值及執行時間。

const arrayData = [{num: 1, time: 500},
  {num: 2, time: 3000},
  {num: 3, time: 1500},
  {num: 4, time: 1000}
];

透過 for...loop 的迴圈形式依序執行 promiseFn 並帶入陣列內的資訊,待前一個執行完畢後才會進入下一個迴圈,待陣列內容執行完畢後則會列出所有的回應結果。

async function seriesFn() {
  const data = [];

  // 依序執行
  for (let i = 0; i < arrayData.length; i++) {
    const item = arrayData[i];
    data.push(await promiseFn(item.num, item.time));
    console.log(item.num, '執行完畢')
  }
  console.log(data); // ["1, 成功", "2, 成功", "3, 成功", "4, 成功"]
}
seriesFn();

平行執行、依序列出

相對於 for...loop 來說 forEach 無法被中斷,並且會幾乎同時運行所有的迴圈內容。

本範例是使用 map 取代 forEach 的方法(兩者概念是接近的)。雖然請求幾乎在同一個時間執行,但執行的時間順序是不確定的,還是會等待執行完成後才統一列出。

async function parallelFn() {
  const data = arrayData.map(async item => {
    const res = await promiseFn(item.num, item.time); // 此行的 await 不會暫停函式運行
    return res;
  })
  console.log(data);

  for (const res of data) {
    console.log(await res);
  }
}
parallelFn();

map 會在執行時就取得結果,此時 data 內的 promise 是尚未 resolve 的狀態,因此需要在後續使用 for...of 等待回應的內容。

時間順序上分別為 500, 3000, 1500, 1000,其中第二個所花的時間較長,因此在第二個執行完成後,剩下以後也會統一列出。


上一篇
JS async/await 系列:基礎概念篇
下一篇
手把手自訂你的 Bootstrap 樣式
系列文
前端常見問題攻略30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言