🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

你還在 for 循環裡使用 await?非同步循環得這樣寫

1. 前言

在循環中使用 await,程式碼看似直觀,但執行時要麼悄無聲息地停止,要麼運行速度緩慢,這是為什麼呢?

本篇聊聊 JavaScript 中的非同步循環問題。

2. 踩坑 1:for 循環裡用 await,效率太低

假設要逐個獲取用戶資料,可能會這樣寫:

const users = [1, 2, 3];
for (const id of users) {
  const user = await fetchUser(id);
  console.log(user);
}

程式碼雖然能運行,但會順序執行——必須等 fetchUser(1) 完成,fetchUser(2) 才會開始。若業務要求嚴格按順序執行,這樣寫沒問題;但如果請求之間相互獨立,這種寫法就太浪費時間了。

3. 踩坑 2:map 裡直接用 await,拿到的全是 Promise

很多人會在 map() 裡用 await,卻未處理返回的 Promise,結果踩了坑:

const users = [1, 2, 3];
const results = users.map(async (id) => {
  const user = await fetchUser(id);
  return user;
});
console.log(results); // 輸出 [Promise, Promise, Promise],而非實際用戶資料

語法上沒問題,但它不會等 Promise resolve。若想讓請求並行執行並獲取最終結果,需用 Promise.all()

const results = await Promise.all(users.map((id) => fetchUser(id)));

這樣所有請求會同時發起results 中就是真正的用戶資料了。

4. 踩坑 3:Promise.all 一錯全錯

Promise.all() 時,只要有一個請求失敗,整個操作就會報錯:

const results = await Promise.all(
  users.map((id) => fetchUser(id)) // 假設 fetchUser(2) 出錯
);

如果 fetchUser(2) 返回 404 或網路錯誤,Promise.all() 會直接 reject,即便其他請求成功,也拿不到任何結果。

5. 更安全的替代方案

5.1. 用 Promise.allSettled(),保留所有結果

使用 Promise.allSettled(),即便部分請求失敗,也能拿到所有結果,之後可手動判斷成功與否:

const results = await Promise.allSettled(users.map((id) => fetchUser(id)));

results.forEach((result) => {
  if (result.status === "fulfilled") {
    console.log("✅ 用戶資料:", result.value);
  } else {
    console.warn("❌ 錯誤:", result.reason);
  }
});

5.2. 在 map 裡加 try/catch,返回兜底值

也可在請求時直接捕獲錯誤,給失敗的請求返回預設值:

const results = await Promise.all(
  users.map(async (id) => {
    try {
      return await fetchUser(id);
    } catch (err) {
      console.error(`獲取用戶${id}失敗`, err);
      return { id, name: "未知用戶" }; // 兜底資料
    }
  })
);

這樣還能避免 “unhandled promise rejections” 錯誤——在 Node.js 嚴格環境下,該錯誤可能導致程式崩潰。

6. 現代非同步循環方案,按需選擇

6.1. for...of + await:適合需順序執行的場景

若下一個請求依賴上一個的結果,或需遵守 API 的頻率限制,可採用此方案:

// 在 async 函數內
for (const id of users) {
  const user = await fetchUser(id);
  console.log(user);
}
// 不在 async 函數內,用立即執行函數
(async () => {
  for (const id of users) {
    const user = await fetchUser(id);
    console.log(user);
  }
})();
  • 優點:保證順序,支持限流
  • 缺點:獨立請求場景下速度慢

6.2. Promise.all + map:適合追求速度的場景

請求間相互獨立且可同時執行時,此方案效率最高:

const usersData = await Promise.all(users.map((id) => fetchUser(id)));
  • 優點:網路請求、CPU 獨立任務場景下速度快
  • 缺點:一個請求失敗會導致整體失敗(需手動處理錯誤)

6.3. 限流並行:用 p-limit 控制並發數

若需兼顧速度與 API 限制,可借助 p-limit 等工具控制同時發起的請求數量:

import pLimit from "p-limit";
const limit = pLimit(2); // 每次同時發起 2 個請求
const limitedFetches = users.map((id) => limit(() => fetchUser(id)));
const results = await Promise.all(limitedFetches);
  • 優點:平衡並發和控制,避免壓垮外部服務
  • 缺點:需額外引入依賴

7. 注意:千萬別在 forEach() 裡用 await

這是一個高頻陷阱:

users.forEach(async (id) => {
  const user = await fetchUser(id);
  console.log(user); // ❌ 不會等待執行完成
});

forEach() 不會等待非同步回調,請求會在背景亂序執行,可能導致程式碼邏輯出錯、錯誤被遺漏。

替代方案:

  • 順序執行:用 for...of + await
  • 並行執行:用 Promise.all() + map()

8. 總結:按需選擇

JavaScript 非同步能力很強,但循環裡用 await 要“按需選擇”,核心原則如下:

需求場景 推薦方案
需保證順序、逐個執行 for...of + await
追求速度、獨立請求 Promise.all() + map()
需保留所有結果(含失敗) Promise.allSettled()/try-catch
需控制並發數、遵守限流 p-limit 等工具

9. 參考鏈接

  1. allthingssmitty.com/2025/10/20/…

原文出處:https://juejin.cn/post/7569402861802782730


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝14   💬4   ❤️5
463
🥈
我愛JS
📝1   💬4   ❤️2
48
🥉
酷豪
1
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付