JavaScript 中的 async/await 是什麼?和 promise 有什麼差別?

2024年3月30日

💎 加入 E+ 成長計畫 與超過 350+ 位軟體工程師一同在社群中成長,並且獲得更多的軟體工程學習資源

在之前的文章中

我們分別討論過事件循環 (Event Loop)、異步(非同步) 和 Promise 的概念,而在這篇文章中,我們將深入探討 async/await 是什麼?以及它與 Promise 的差異。

async/await 是什麼?

在 JavaScript 中,async/await 是一種讓異步(非同步)操作更容易理解和管理的語法。它建立在 Promise 的基礎上,但提供了更簡潔、更直觀的方式來處理異步操作。

以下我們先把 asyncawait 拆開來看:

async 語法

使用 async 關鍵字聲明的函式為異步函式,異步函式會返回一個 Promise 物件,而非直接返回函式執行的結果。讓我們透過範例來了解:

  • 下方普通函式 f1() 直接返回字串 "Hello! ExplainThis!"
// 普通函式
function f1() {
  return "Hello! ExplainThis!";
}

f1(); // 輸出: "Hello! ExplainThis!"
  • 下方程式碼中, async function f2() {...} 定義了一個名為 f2 的異步函式,該函式返回字串 "Hello! ExplainThis!",並將其封裝在一個 Promise 物件中。
// 異步函式
async function f2() {
  return "Hello! ExplainThis!";
}

f2(); // 輸出: Promise {<fulfilled>: 'Hello! ExplainThis!'}

上方程式碼寫法跟下方寫法其實是相同的,因為使用 async 時,會自動將回傳值包裝在一個 Promise 物件當中。

// 異步函式
function f3() {
  return Promise.resolve("Hello! ExplainThis!");
}

f3(); // 輸出: Promise {<fulfilled>: 'Hello! ExplainThis!'}

由於 async 函式總是返回一個 Promise 對象,如果要獲取該 Promise 的解析值,可以使用 .then() 方法:

async function f2() {
  return "Hello! ExplainThis!";
}

f2().then((result) => {
  console.log(result); // "Hello! ExplainThis!"
});

await 語法

await 是一個運算子,用於等待一個 Promise 完成或拒絕。它通常與 async 函式一起使用,因為只有在 async 函式內部或模組的頂層,才能使用 await

當使用 await 時,程式會暫停執行該 async 函式,直到 await 等待的 Promise 完成並回傳結果後,才會繼續往下執行。讓我們透過下方範例來了解:

async function getData() {
  // await 等待 fetch 這個非同步函式返回一個 Promise 並解析它
  const res = await fetch("https://example.com/data");

  // await 等待上一步的 Promise 解析後,再解析它的 JSON 資料
  const data = await res.json();

  // 前面兩步都完成後,才會執行這一行並印出資料
  console.log(data);
}

getData();

使用 await 要注意的幾點

  • 在非 async 函式中使用 await 會報 SyntaxError 的錯誤
function f() {
  let promise = Promise.resolve("Hello! ExplainThis!");
  let result = await promise;
}

// Uncaught SyntaxError: await is only valid in async functions and the top level bodies of modules
  • 頂層 await (Top level await)

頂層 await 是一個在 JavaScript 中引入的新語法功能,它允許在模組的最頂層使用 await 關鍵字。在 ES 模組中,原本只有 async 函式內部才能使用 await。但是使用頂層 await 後,就可以直接在模組頂層使用 await 了。

例如,如果想在某個模組中獲取遠端資源,以前需要這樣寫:

import getData from "./getData.js";

let data;

getData().then((result) => {
  data = result;
  // ...使用data
});

有了頂層 await (Top level await),你就可以這樣更直接地寫:

const data = await getData();
// ...使用data

如何使用 async/await

使用 async/await 可以將異步程式碼寫成類似同步的形式,使其更易讀、且更易維護。讓我們先看一個使用 Promise 寫的 getData 函式範例:

我們先來看用 Promise 來寫一個 getData 函式的例子:

function getData(url) {
  return new Promise((resolve, reject) => {
    fetch(url)
      .then((res) => res.json())
      .then((data) => resolve(data))
      .catch((error) => reject(error));
  });
}

getData("https://example.com/data")
  .then((data) => console.log(data))
  .catch((error) => console.error(error));

在這個例子中,getData 函式使用 Promise 來處理異步操作。我們需要使用 .then().catch() 方法來獲取結果或錯誤。

現在,我們使用 async/await 來重寫 getData 函式:

async function getData(url) {
  try {
    const res = await fetch(url);
    const data = await res.json();
    console.log(data);
  } catch (error) {
    console.error(error);
  }
}

getData("https://example.com/data");

在這個例子中:

  1. 使用 async 關鍵字定義一個異步函式,該函式會返回一個 Promise 對象。
  2. 在異步函式中,使用 await 等待 Promise 的完成,並直接返回結果。
  3. 使用 try...catch 捕獲錯誤,使得錯誤處理更加方便和直觀。

可以看到,使用 async/await 後,程式碼變得更加清晰和易於理解。

async/await 與 Promise 的差別?

async/await 和 Promise 都是用於處理異步操作的方式,但它們有以下一些差異:

  1. 語法: async/await 提供了更簡潔、更直觀的語法,使得異步程式碼更易讀和維護。Promise 則需要使用 thencatch 方法來處理結果和錯誤,語法上較為冗長。
  2. 錯誤處理: 在 async/await 中,可以直接使用 try...catch 來捕獲錯誤,而在 Promise 中需要使用 catch 方法。
  3. 程式碼流程: async/await 可以使異步程式碼看起來更像同步程式碼,更容易閱讀和理解。Promise 的程式碼流程則較為不連貫。
🧵 如果你想收到最即時的內容更新,可以在 FacebookInstagram 上追蹤我們