掌握 JavaScript async/await:從入門到進階的完整指南 2025

作者: Calpa Liu
字數:1992
出版:March 30, 2025
分類: 前端開發 JavaScript Promises Callbacks 效能優化 最佳實踐

深入剖析 JavaScript 非同步程式設計的最新發展!從基礎概念到進階應用,本文完整介紹 async/await 的強大功能、最佳實踐和效能優化技巧。透過實用範例,了解如何運用 async/await 提升程式碼品質並避免常見陷阱。

非同步程式設計的演進

JavaScript 是一種單線程語言,為了避免長時間運行的操作阻塞用戶界面,非同步程式設計成為必要。在 JavaScript 的發展歷史中,處理非同步操作的方式經歷了三個主要階段:

回調函數 (Callbacks)

最初,JavaScript 透過回調函數處理非同步操作。然而,當處理多個相互依賴的非同步操作時,常常導致所謂的「回調地獄」(Callback Hell),造成程式碼可讀性和維護性的挑戰。

Promises

ES2015 (ES6) 引入了 Promise 物件,提供了一種更結構化的方式來處理非同步操作。Promise 是代表未來會產生值的第一類物件,可以輕鬆地傳遞、組合和轉換。透過 .then().catch() 方法,它改善了回調地獄的問題,但仍有其局限性。

async/await

ES2017 引入了 async/await 語法,建立在 Promise 的基礎上,提供了更直觀的方式來編寫和理解非同步程式碼。async/await 允許開發者以看似同步的方式編寫非同步程式碼,大大提升了程式碼的可讀性和維護性。

async/await 的基本概念與語法

async/await 由兩個關鍵字組成:

  1. async:用於宣告一個非同步函數,該函數將始終返回一個 Promise。
  2. await:只能在 async 函數內部使用,用於暫停函數執行,直到 Promise 解決。

基本語法示例:

async function fetchData(url) {
  try {
    const response = await fetch(url);
    const data = await response.json();
    return data;
  } catch (error) {
    console.error(error);
  }
}

async/await 相較於 Promises 的優勢

1. 更清晰的程式碼結構與可讀性

async/await 使非同步程式碼看起來與同步程式碼相似,減少了鏈式調用的複雜性,使程式碼更容易閱讀和理解。

使用 Promise:

const makeRequest = () =>
  getJSON().then((data) => {
    console.log(data);
    return "done";
  });

使用 async/await:

const makeRequest = async () => {
  console.log(await getJSON());
  return "done";
};

這種結構使得程式碼更加線性和直觀,特別是在處理多個非同步操作時。

2. 更自然的錯誤處理

async/await 允許使用傳統的 try/catch 結構來處理同步和非同步錯誤,使錯誤處理更加一致和直觀。

使用 Promise:

const makeRequest = () => {
  try {
    getJSON().then((result) => {
      const data = JSON.parse(result);
      console.log(data);
    });
  } catch (err) {
    console.log(err);
  }
};

使用 async/await:

const makeRequest = async () => {
  try {
    const data = JSON.parse(await getJSON());
    console.log(data);
  } catch (err) {
    console.log(err);
  }
};

在 Promise 版本中,try/catch 塊無法捕獲 Promise 內部的錯誤,而在 async/await 版本中,所有錯誤都能被適當捕獲。

3. 更容易的調試體驗

使用 async/await 進行調試比調試 Promises 更加容易。在調試 Promises 時,有兩個主要問題:

  1. 無法在返回表達式的箭頭函數中設置斷點
  2. 當使用斷點調試步進功能時,調試器不會移動到下一個 .then 塊,因為它只步進同步程式碼

相比之下,使用 async/await,可以像調試同步程式碼一樣,設置斷點並步進執行非同步操作。

4. 更優雅地處理條件邏輯

async/await 使處理條件邏輯變得更加簡潔。當需要基於前一個非同步調用的結果進行條件操作時,Promise 鏈變得複雜且難以閱讀。

使用 async/await:

const makeRequest = async () => {
  const data = await getJSON();
  if (data.needsAnotherRequest) {
    const moreData = await makeAnotherRequest(data);
    console.log(moreData);
    return moreData;
  } else {
    console.log(data);
    return data;
  }
};

這種方式比使用 Promises 的嵌套 .then() 調用更清晰易讀。

5. 更簡單地處理中間值

在需要多個非同步操作並且後續操作依賴前一個操作結果的情況下,async/await 特別有用。

使用 async/await:

const makeRequest = async () => {
  const value1 = await promise1();
  const value2 = await promise2(value1);
  return promise3(value1, value2);
};

與嵌套的 Promise 鏈相比,這種代碼結構顯著簡化了複雜的非同步流程。

async/await 的進階特性與效能優化

Top-level await

ES2022 引入了 top-level await,允許在模組頂層使用 await,無需包裝在 async 函數中:

// config.js
const config = await fetch("/api/config");
export default await config.json();

AbortController 整合

async/await 可以與 AbortController 完美配合,實現請求取消功能:

const controller = new AbortController();
const { signal } = controller;

const fetchData = async () => {
  try {
    const response = await fetch("https://api.example.com/data", { signal });
    const data = await response.json();
    return data;
  } catch (error) {
    if (error.name === "AbortError") {
      console.log("請求已取消");
    } else {
      throw error;
    }
  }
};

// 5 秒後取消請求
setTimeout(() => controller.abort(), 5000);

async/await 在應用中的效能與擴展性

提升 Web 應用的效能與擴展性

記憶體管理優化

使用 async/await 時,需要注意記憶體管理。以下是一些優化建議:

// 不佳的做法 - 可能導致記憶體洩漏
const processItems = async (items) => {
  const results = [];
  for (const item of items) {
    results.push(await processItem(item));
  }
  return results;
};

// 優化後的做法 - 使用 for...of 進行迭代
const processItems = async (items) => {
  const results = [];
  for await (const item of items) {
    const result = await processItem(item);
    results.push(result);
  }
  return results;
};

// 更好的做法 - 使用 Promise.all 並控制並發數
const processItemsInBatches = async (items, batchSize = 5) => {
  const results = [];
  for (let i = 0; i < items.length; i += batchSize) {
    const batch = items.slice(i, i + batchSize);
    const batchResults = await Promise.all(
      batch.map((item) => processItem(item))
    );
    results.push(...batchResults);
  }
  return results;
};

效能監控

在生產環境中監控 async/await 的效能:

const measureAsyncOperation = async (operation) => {
  const start = performance.now();
  try {
    const result = await operation();
    const duration = performance.now() - start;
    console.log(`操作完成,耗時:${duration}ms`);
    return result;
  } catch (error) {
    const duration = performance.now() - start;
    console.error(`操作失敗,耗時:${duration}ms`, error);
    throw error;
  }
};

在 Web 應用程序中,特別是服務器端應用(如 ASP.NET),async/await 能夠顯著提高應用程序的擴展性。當執行 I/O 密集型操作(如數據庫查詢或 API 調用)時,使用 async/await 可以釋放線程,使其能夠處理其他請求,從而提高整體吞吐量。

更有效地處理多個非同步操作

async/await 與 Promise.all() 結合使用時,可以有效地並行處理多個非同步操作。

const makeRequest = async () => {
  const [value1, value2, value3] = await Promise.all([
    promise1(),
    promise2(),
    promise3(),
  ]);
  return processValues(value1, value2, value3);
};

2025 年的最佳實踐與注意事項

新的錯誤處理模式

使用新的 Error Cause API 提供更詳細的錯誤信息:

const fetchUserData = async (userId) => {
  try {
    const response = await fetch(`/api/users/${userId}`);
    if (!response.ok) {
      throw new Error("獲取用戶數據失敗", {
        cause: {
          code: response.status,
          url: response.url,
          userId,
        },
      });
    }
    return await response.json();
  } catch (error) {
    console.error("錯誤詳情:", error.cause);
    throw error;
  }
};

使用結構化並發

const processUserData = async () => {
  try {
    const results = await Promise.allSettled([
      fetchUserProfile(),
      fetchUserPosts(),
      fetchUserPreferences(),
    ]);

    const [profile, posts, preferences] = results.map((result) =>
      result.status === "fulfilled" ? result.value : null
    );

    return {
      profile,
      posts,
      preferences,
      timestamp: new Date(),
    };
  } catch (error) {
    console.error("處理用戶數據時發生錯誤:", error);
    throw error;
  }
};

何時使用 async/await

  • I/O 密集型操作,如網路請求、檔案存取和資料庫操作
  • 處理複雜的非同步流程和依賴關係
  • 需要清晰錯誤處理的情況

何時避免使用 async/await

  • CPU 密集型操作可能導致效能下降
  • 過度使用可能會不必要地增加複雜性
  • 當不需要等待操作完成時,可能不需要非同步處理

常見陷阱

  • 忘記在函數前添加 async 關鍵字
  • 在非同步函數中忘記使用 await
  • 產生不必要的序列化操作,而不是使用 Promise.all() 進行並行處理

結論

async/await 不僅僅是 Promise 的語法糖,而是提供了真正的優勢,使非同步程式碼更具表現力、可讀性和可維護性。它統一了非同步程式設計的經驗,提供了更好的錯誤堆疊追蹤,並允許使用所有同步程式設計中可用的語言結構。

隨著 Web 開發不斷向更複雜的應用演進,掌握 async/await 成為現代 JavaScript 開發者的必備技能。對於需要處理大量非同步操作的專案,使用 async/await 可以顯著提高程式碼質量和開發效率。

雖然 async/await 提供了許多優勢,但理解其底層原理以及與 Promises 的關係仍然重要,這有助於在各種情況下選擇最合適的非同步處理方式。

感謝您閱讀我的文章。歡迎隨時分享你的想法。
前端開發 JavaScript Promises Callbacks 效能優化 最佳實踐
關於 Calpa

Calpa 擅長使用 TypeScriptReact.jsVue.js 建立 Responsive Website。

他積極參與開源社區,曾在 2019 年的香港開源大會上擔任講者,提供工作經驗和見解。此外,他也在 GitHub 上公開分享個人博客程式碼,已獲得超過 300 顆星星和 60 個分支的支持。

他熱愛學習新技術,並樂意分享經驗。他相信,唯有不斷學習才能跟上快速演變的技術環境。

熱門文章

最新文章