LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

你在 forEach 里写的 await,其实根本没在等!

admin
2025年7月1日 9:36 本文热度 193

forEach和 async/await的这个组合,就像一对貌合神离的“情侣”,看起来般配,实则互相“背叛”。这个坑,我结结实实地踩过,而且不止一次。

故事的开始:一个看似无害的需求

想象一下,接到一个需求:批量更新一组用户的状态。后端提供了一个接口 updateUser(userId),它是一个返回 Promise 的异步函数。第一反应可能就是这样写:

const userIds = [1, 2, 3, 4, 5];

async function updateUserStatus(id) {
 console.log(`开始更新用户 ${id}...`);
 // 模拟一个需要 1 秒的网络请求
 await new Promise(resolve => setTimeout(resolve, 1000)); 
 console.log(`✅ 用户 ${id} 更新成功!`);
 return { success: true };
}

async function batchUpdateUsers(ids) {
 console.log("--- 开始批量更新 ---");

  ids.forEach(async (id) => {
    await updateUserStatus(id);
  });

 console.log("--- 所有用户更新完毕!---"); // ⚠️ 问题的根源在这里!
}

batchUpdateUsers(userIds);

运行这段代码,控制台输出了什么?不是期望的按顺序等待,而是这样的结果:

看到了吗?“所有用户更新完毕!”这句话几乎是立即打印出来的,它根本没有“等待”任何 updateUserStatus函数的完成。

问题剖析:forEach到底干了什么?

forEach被设计为同步迭代器。它的工作很简单:遍历数组中的每个元素,并为每个元素同步地调用你提供的回调函数。它不关心你的回调函数是同步的还是异步的,也不关心它返回什么。

换句话说,forEach的内心独白是:

“我的任务就是触发,触发,再触发。至于你传进来的那个 async函数什么时候执行完?抱歉,那不归我管,我不会等它的。”


正确的姿势:如何真正地“等待”?

既然 forEach不行,那我们该用什么?答案是使用那些“懂” Promise 的循环方式。

方案一:老实人 for...of循环(顺序执行)

如果我们需要按顺序、一个接一个地执行异步操作,for...of循环是你的最佳选择。它是 async/await的天作之合。

async function batchUpdateUsersInOrder(ids) {
  console.log("--- 开始批量更新 (顺序执行) ---");

  for (const id of ids) {
    // 这里的 await 会实实在在地暂停 for 循环的下一次迭代
    await updateUserStatus(id); 
  }

  console.log("--- 所有用户更新完毕!(这次是真的) ---");
}

运行结果:

这完全符合我们的直觉:等待上一个完成后,再开始下一个。

方案二:效率先锋 Promise.allmap(并行执行)

在很多场景下,我们并不需要严格地按顺序执行。这些异步任务之间没有依赖关系,完全可以并行处理以提高效率。这时,map和 Promise.all的组合就闪亮登场了。

  1. Array.prototype.map:与 forEach不同,map会返回一个新数组。当我们给它一个 async函数时,它会同步地返回一个由 pendingPromise 组成的数组。
  2. Promise.all:这个方法接收一个 Promise 数组,并返回一个新的 Promise。只有当数组中所有的 Promise 都成功完成(resolved)时,这个新的 Promise 才会完成。
async function batchUpdateUsersInParallel(ids) {
 console.log("--- 开始批量更新 (并行执行) ---");

 // 1. map 会立即返回一个 Promise 数组
 const promises = ids.map(id => updateUserStatus(id));

 // 2. Promise.all 会等待所有 promises 完成
 await Promise.all(promises);

 console.log("--- 所有用户更新完毕!(这次是真的,而且很快) ---");
}

运行结果:

这种方式的总耗时约等于最慢的那个异步任务的耗时,效率极高。

方案三:更灵活的 for...in和传统 for循环

for...in(用于遍历对象键)和传统的 for (let i = 0; ...)循环同样支持 await。它们的工作方式与 for...of类似,都会等待 await的 Promise 完成。

// 传统 for 循环
for (let i = 0; i < ids.length; i++) {
  await updateUserStatus(ids[i]);
}

为了防止你和我一样踩坑,这里有一份速记备忘录:需要按顺序执行使用 for...of;需要并行执行,提高效率使用 Promise.allmap,性能最佳,但要注意并发数过高可能带来的问题;绝对不要用 forEach,它不会等待我们的 await,它只会无情地触发。


阅读原文:https://mp.weixin.qq.com/s/QUynwSg3aBjzsNo5WttXDQ


该文章在 2025/7/1 9:36:26 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved