iT邦幫忙

2023 iThome 鐵人賽

DAY 20
0

Async / Await 是 ECMAScript 8 ( ES2017 ) 引入的一個功能,它是建立在 Promise 之上的語法糖,允許我們使用 async 關鍵字標記一個函式,表示這個函式是非同步的,使其返回一個 Promise。在標記為 async 的函式內部,我們可以使用 await 關鍵字等待 Promise 物件的取得或失敗,程式碼將等待非同步操作完成後,才會繼續執行下一行程式碼。這使得我們能夠 以同步的方式來處理非同步操作,避免了回呼地獄,同時保持了程式的性能優勢。

我們使用 Async / Await 繼續改寫 上一章 波動拳 code 的範例:

const asyncUser = (id: number): Promise<string> => {
  return new Promise((resolve, _) => {
    setTimeout(() => {
      resolve(`我是第 ${id} 個`);
    }, 1000);
  });
};

const processData = async () => {
  const res1 = await asyncUser(1);
  console.log(res1); // 輸出: 我是第 1 個

  const res2 = await asyncUser(2);
  console.log(res2); // 輸出: 我是第 2 個

  const res3 = await asyncUser(3);
  console.log(res3); // 輸出: 我是第 3 個

  const res4 = await asyncUser(4);
  console.log(res4); // 輸出: 我是第 4 個

  const res5 = await asyncUser(5);
  console.log(res5); // 輸出: 我是第 5 個
};

processData();

在這個範例中,我們定義了一個 asyncUser 函式,用於模擬非同步操作的延遲。然後,我們定義了一個 processData 函式,使用 async 關鍵字標記,內部使用了 await 關鍵字等待 asyncUser 函式的執行,最後將會依序每秒輸出。這使得我們能夠以同步的風格來描述非同步操作的順序,程式碼的執行過程更加清晰可讀。

錯誤處理

還記得 Promise 是使用 .then() 和 .catch() 的方式來處理成功和失敗吧,而 Async / Await 則是使用 try/catch 的方式來捕捉非同步操作中的錯誤,並可以適時地使用 throw 來拋出錯誤以進行排除故障。

  • try 區塊: 在這個區塊中,我們會放置成功或判斷可能引發異常的程式碼,如果錯誤發生,會立即跳轉到 catch 區塊。

  • catch 區塊: 在這個區塊中,我們可以處理捕捉到的錯誤,並根據情況進行適當的調整。

所以當一個 Promise 失敗時 ( rejected ),await 會抛出一個錯誤異常,我們可以在 catch 區塊中捕獲這個錯誤。

以下是一個包含錯誤處理的範例:

interface IData {
  userId: number;
  id: number;
  title: string;
  body: string;
}

async function fetchRemoteData(): Promise<IData> {
  try {
    const res = await fetch("https://jsonplaceholder.typicode.com/posts/1");

    // promise 失敗則拋出異常
    if (!res.ok) throw new Error("取得資料失敗!");
    
    const data: IData = await res.json();
    return data;
  } catch (error) {
    throw error;
  }
}

async function fetchData() {
  try {
    const remoteData = await fetchRemoteData();
    console.log(remoteData);
  } catch (error) {
    console.error(error);
  }
}

fetchData();

在這個範例中,我們使用了 fetchData 函式來獲取數據,並使用 await 等待數據的返回。如果獲取過程中出現錯誤,await 將會拋出一個異常,我們可以在 catch 區塊中捕獲這個異常並進行錯誤處理。

不過如果我們使用的是 error.message 則會發生 error 的類型為 unknown 的錯誤。

https://ithelp.ithome.com.tw/upload/images/20230920/201412506Lf2IRs1Z7.png

有以下幾種解法:

  • 將 tsconfig.json 裡的嚴格模式 strict 改為 false
// tsconfig.json
{
  "compilerOptions": {
    // ... ,
    "strict": false
    // ...,
  }
}
  • 將 tsconfig.json 裡的 useUnknownInCatchVariables 改為 false
// tsconfig.json
{
  "compilerOptions": {
    // ... ,
    "strict": true,
    "useUnknownInCatchVariables": false
    // ...,
  }
}
  • 使用 斷言為 Error 的方式:
(async () => {
  try {
    // ...
  } catch (error) {
    console.error((error as Error).message); 
    // 或是寫成 console.error((<Error>error).message);
  }
})();
  • 使用 instanceof 的方式:
(async () => {
  try {
    // ...
  } catch (error) {
    if (error instanceof Error) {
      console.error(error.message);
    }
  }
})();
  • 最偷懶的方式:直接使用 any
(async () => {
  try {
    // ...
  } catch (error: any) {
    console.error(error.message);
  }
})();

以上幾種寫法都可以消除 error 的類型為 unknown 的錯誤,可以看個人、團隊或專案任選一種即可。

https://ithelp.ithome.com.tw/upload/images/20230920/20141250jNmzjxkPFq.png

同時處理多個 Promise

昨天我們有提到使用 Promise all 來同時處理多個非同步操作,等待它們全部完成後再繼續執行。今天我們加上 Async / Await 來處理這種情況。

看以下範例:

interface IData {
  userId: number;
  id: number;
  title: string;
  body: string;
}

const fetchRemoteData = async (): Promise<IData[]> => {
  const promiseAllData = [
    fetch("https://jsonplaceholder.typicode.com/posts/1"),
    fetch("https://jsonplaceholder.typicode.com/posts/2"),
    fetch("https://jsonplaceholder.typicode.com/posts/3"),
  ];

  try {
    const res = await Promise.all(promiseAllData);

    // 確保全部都成功取得資料
    res.forEach((item) => {
      if (!item.ok) throw new Error("取得資料失敗!");
    });

    const data: IData[] = await Promise.all(res.map((item) => item.json()));
    return data;
  } catch (error) {
    throw error;
  }
};

const fetchData = async () => {
  try {
    const remoteData = await fetchRemoteData();
    console.log(remoteData);
  } catch (error) {
    console.error((error as Error).message);
  }
};

fetchData();

上面的範例,我們同時發起多個數據請求,然後使用 Promise.all 等待所有的 Promise 結束,這樣我們就可以確保所有的非同步操作都完成後,再一次性處理它們的結果。

使用 Async / Await 可以使我們以更直觀、更具同步風格的方式來處理非同步操作,讓程式碼更具可讀性和可維護性,還能夠保持非同步操作的性能優勢。

選擇使用 Promise 或 Async / Await

Promise

  • 是一個相對較簡單的非同步處理方式,特別 適合於處理單一非同步操作
  • 使用 .then() 和 .catch() 的鏈式操作,讓我們可以輕鬆地處理多個非同步操作。

Async / Await

  • 適合用於較複雜的非同步流程,如多個非同步操作之間的協調。
  • 使非同步代碼更具可讀性,因為它看起來像同步代碼,易於理解。
  • 使用 try/catch 可以更容易地捕捉和處理錯誤。
  • 通常 程式碼更加簡潔
  • 雖然 Async / Await 也支持鏈式操作,但不像 Promise 那麼明顯,且不需要多次調用 .then()。
Promise Async / Await
簡單性 ✔︎
簡潔性 ✔︎
維護性 ✔︎
可讀性 ✔︎ ✔︎
鏈式操作 ✔︎ ✔︎
錯誤處理 .then()、.catch() try / catch
適用範圍 較簡單的非同步流程 較複雜的非同步流程
JavaScript 版本 ECMAScript 2015 ( ES6 ) ECMAScript 2017 ( ES8 )

所以不管是選擇使用 Promise 還是 Async / Await ,還是取決於程式碼結構和專案需求。對於簡單的非同步操作,Promise 可能更適合,而對於較複雜的非同步操作,特別是需要錯誤處理和協調的情況,Async / Await 可能會是更好的方式。


上一篇
非同步處理 Ⅰ (Promise)
下一篇
泛型(Generics)
系列文
用不到 30 天學會基本 TypeScript30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言