Stories

Detail Return Return

JavaScript 異步編程指南:async/await 與 Promise 該怎麼選? - Stories Detail

在 JavaScript 開發中,異步操作就像家常便飯 —— 從調用後端 API 到讀取本地文件,幾乎無處不在。但很多開發者都會困惑:到底該用 Promise 的鏈式調用,還是 async/await 語法?其實答案很簡單:沒有絕對的好壞,只有場景的適配

今天我們就用實際案例聊聊,這兩種異步寫法各自適合什麼場景,以及如何在項目中混搭使用,讓代碼既高效又易讀。

先搞懂:兩者不是對立關係

很多人以為 async/await 是 Promise 的替代品,其實大錯特錯。async/await 本質是 Promise 的語法糖,它的底層依然是 Promise 實現。就像用for...of遍歷數組比forEach更直觀一樣,async/await 讓異步代碼看起來更像同步代碼。

先看個最簡單的對比:

// Promise寫法
fetchData().then(data => {
  return processData(data);
}).then(result => {
  console.log(result);
}).catch(err => {
  console.error(err);
});

// async/await寫法
async function handleData() {
  try {
    const data = await fetchData();
    const result = await processData(data);
    console.log(result);
  } catch (err) {
    console.error(err);
  }
}

兩者功能完全一致,但 async/await 的線性結構更符合人類的閲讀習慣 —— 這也是它被廣泛採用的核心原因。

優先用 async/await 的 3 種場景

什麼時候用 async/await 更合適?記住一個原則:當異步操作需要按順序執行,或者邏輯中有較多條件判斷時

1. 線性異步流程(一步接一步)

最典型的場景是依賴前一步結果的異步操作。比如先登錄獲取 token,再用 token 獲取用户信息,最後用用户信息加載權限配置:

// 用async/await,邏輯一目瞭然
async function initApp(username, password) {  // 補充參數定義,避免未定義變量
  try {
    const token = await login(username, password);
    const userInfo = await getUserInfo(token);  // 依賴token
    const permissions = await getPermissions(userInfo.role);  // 依賴userInfo
    renderApp(permissions);
  } catch (err) {
    showError(err);
  }
}

如果用 Promise 鏈式調用寫,雖然能實現,但嵌套越深(比如再加兩步),可讀性會明顯下降。

2. 包含條件判斷的異步邏輯

當異步流程中需要根據結果做分支判斷時,async/await 的優勢更明顯。比如:

async function checkAndUpdate() {
  const currentVersion = await getCurrentVersion();
  
  // 同步的條件判斷,自然融入異步流程
  if (currentVersion < '2.0') {
    const updateInfo = await fetchUpdateInfo();
    if (updateInfo && updateInfo.force) {  // 增加可選鏈,避免updateInfo為undefined時報錯
      await showForceUpdateDialog();
    } else {
      await showOptionalUpdateToast();
    }
  }
}

這段代碼用 Promise 寫會嵌套多層then,而 async/await 讓同步邏輯和異步操作無縫銜接,就像寫同步代碼一樣自然。

3. 需要中斷執行的場景

有時候我們需要在異步流程中提前返回(比如參數無效時),async/await 的寫法更直觀:

async function submitForm(data) {
  // 同步校驗
  if (!data?.email) {  // 增加可選鏈,避免data為null/undefined時報錯
    showError('郵箱不能為空');
    return;  // 直接中斷
  }
  
  // 異步操作
  try {
    const validateResult = await validateRemote(data);
    if (!validateResult.success) {
      showError(validateResult.message);
      return;  // 校驗失敗,中斷
    }
    await submitData(data);
    showSuccess();
  } catch (err) {
    handleError(err);
  }
}

用 Promise 的話,需要在每個then裏處理條件判斷,代碼會更零散。

優先用 Promise 的 3 種場景

雖然 async/await 很方便,但有些場景下,Promise 的原生 API(如Promise.allPromise.race)更適合,甚至不可替代。

1. 並行執行多個異步操作

當多個異步任務互不依賴時,用Promise.all並行執行能大幅提高效率。比如同時加載列表數據和篩選條件:

async function loadDashboard() {
  // 兩個請求並行執行,總耗時是較慢那個的時間
  const [products, categories] = await Promise.all([
    fetchProducts(),
    fetchCategories()
  ]);
  
  renderProducts(products);
  renderFilters(categories);
}

如果用await逐個調用,會變成串行執行(總耗時是兩者之和),完全沒必要:

// 不推薦:串行執行,浪費時間
const products = await fetchProducts();
const categories = await fetchCategories();  // 等第一個完成才開始

2. 需要超時控制的異步操作

Promise.race可以實現 “誰先完成就用誰的結果”,非常適合超時控制。比如 “3 秒內沒返回就顯示加載失敗”:

// 封裝一個帶超時的異步函數
function withTimeout(promise, timeoutMs = 3000) {
  let timer;  // 將timer提升到外部作用域
  const timeoutPromise = new Promise((_, reject) => {
    timer = setTimeout(() => {
      reject(new Error('請求超時'));
    }, timeoutMs);
  });
  return Promise.race([
    promise,
    timeoutPromise
  ]).finally(() => clearTimeout(timer));  // 確保始終清除定時器
}

// 使用
async function loadData() {
  try {
    const data = await withTimeout(fetchLargeData());
    render(data);
  } catch (err) {
    showError(err.message);  // 可能是超時錯誤
  }
}

這段代碼用 async/await 無法實現,必須依賴 Promise 的race方法。

3. 處理動態數量的異步任務

當需要處理不確定數量的異步操作(比如批量上傳多個文件),Promise.all是最佳選擇:

async function uploadFiles(files) {
  if (!files?.length) return;  // 增加空值判斷,避免空數組或undefined時執行無效操作
  
  // 生成一個包含所有上傳Promise的數組
  const uploadPromises = files.map(file => {
    return uploadFile(file);  // 每個文件的上傳是異步操作
  });
  
  // 等待所有文件上傳完成
  const results = await Promise.all(uploadPromises);
  
  // 處理結果
  const successCount = results.filter(r => r?.success).length;  // 增加可選鏈容錯
  showMessage(`成功上傳 ${successCount}/${files.length} 個文件`);
}

這種動態場景下,Promise 的數組處理能力比 async/await 更高效。

混搭使用:發揮各自優勢

實際開發中,兩者往往結合使用效果最好。比如先並行獲取基礎數據,再串行處理後續邏輯:

async function buildReport() {
  // 第一步:並行獲取不相關的數據(提高效率)
  const [users, orders, products] = await Promise.all([
    fetchUsers(),
    fetchOrders(),
    fetchProducts()
  ]);
  
  // 第二步:串行處理依賴關係的邏輯
  const userStats = await calculateUserStats(users);
  const orderSummary = await generateOrderSummary(orders, userStats);  // 依賴userStats
  const report = await compileReport(orderSummary, products);  // 依賴前兩者
  
  return report;
}

這段代碼先用Promise.all並行請求,節省時間;再用 async/await 處理有依賴的串行邏輯,兼顧效率和可讀性。

避坑指南:這些錯誤別犯

  • 不要在循環中直接用 await

循環中用await會導致串行執行,如需並行,改用Promise.all

// 錯誤:串行執行,慢!
for (const file of files) {
  await uploadFile(file);
}
// 正確:並行執行,快!
await Promise.all(files.map(file => uploadFile(file)));
  • 別忘了 try/catch

async/await 中任何await的 Promise reject 都會觸發異常,必須用try/catch捕獲,否則會導致程序崩潰。

  • 不要把 async 函數當同步函數用

async 函數永遠返回 Promise,調用時必須用await.then處理,直接調用會拿到 Promise 對象而非結果。

  • 避免過度封裝

簡單的異步操作(比如單個請求)沒必要包成 async 函數,直接返回 Promise 更簡潔。

  • 注意 Promise.all 的失敗快速失敗特性

Promise.all中任何一個 Promise reject 都會立即觸發整個 Promise reject,如需等待所有結果(無論成功失敗),可使用Promise.allSettled

// 等待所有任務完成,無論成功失敗
// 文件上傳場景優化
const results = await Promise.allSettled(uploadPromises);
const failedFiles = results
  .filter(r => r.status === 'rejected')
  .map((r, i) => ({ file: files[i].name, error: r.reason }));

總結:一句話記住用法

  • 用 async/await:處理線性依賴、包含條件判斷、需要中斷的異步流程;
  • 用 Promise API:處理並行任務(all)、超時控制(race)、動態異步數組;
  • 最佳實踐:兩者結合,並行任務用Promise.all,後續邏輯用 async/await 串聯。

説到底,選擇的核心是可讀性和效率—— 哪種寫法讓團隊成員更容易理解,哪種方式能讓程序跑得更快,就用哪種。技術沒有絕對的好壞,適合場景的才是最好的。

user avatar tianmiaogongzuoshi_5ca47d59bef41 Avatar aitinggedejinzhengu Avatar sovitjs Avatar shuirong1997 Avatar xiaolei_599661330c0cb Avatar romanticcrystal Avatar it1042290135 Avatar 79px Avatar shuihuyangpinga Avatar xiangjiaochihuanggua Avatar elegantdevil Avatar huggingface Avatar
Favorites 21 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.