Showing Posts From

异步编程

为什么 forEach 里的 await 没用:JS 异步循环踩坑记

前几天写脚本,想在数组遍历里挨个 await 一下: const arr = [1, 2, 3];arr.forEach(async (item) => { await sleep(1000); console.log(item); });console.log('结束');预期看到 1 2 3 结束,结果输出是: 结束 1 2 3forEach 完全没等异步回调结束,console.log('结束') 就先跑了。 为什么 forEach 不等待 看 Array.prototype.forEach 的内部实现,本质就是: for (let i = 0; i < arr.length; i++) { callback(arr[i]); }它 不关心 callback 返回的是不是 Promise,也不收集 Promise,更不会 await。所以哪怕你写: await arr.forEach(async x => await fn(x));等价于: await undefined因为 forEach 的返回值就是 undefined。 正确写法 需要顺序 await:用 for...of 一个执行完再执行下一个,最稳、最好读: for (const item of arr) { await doSomething(item); }适合请求限流、数据库写入、有依赖关系的自动化脚本。 需要并发 await:用 Promise.all + map 全部同时开跑,最快: await Promise.all( arr.map(async (item) => { await doSomething(item); }) );注意别把上游接口打爆——需要限流的话可以配合 p-limit 这类库。 记忆表场景 写法顺序 await for...of并发 await Promise.all + map不需要等待 forEach 也无所谓一条经验 只要方法里出现 await,就别用 forEach。 想清楚这一步之后,很多"数据处理完了但结果不对"的 bug 就会消失。

JavaScript forEach 不支持 await:for...of 顺序执行与 Promise.all 并发执行

Array.prototype.forEach 不会等待异步回调完成,这是 JavaScript 里最常见的异步陷阱之一。 问题演示 const arr = [1, 2, 3];arr.forEach(async (item) => { await sleep(1000); console.log(item); });console.log('结束');输出顺序是: 结束 1 2 3forEach 本身是同步的,不关心回调返回的是否是 Promise,调用后立即继续执行。 await arr.forEach(...) 也没有意义,因为 forEach 的返回值始终是 undefined: await undefined // 无效等待顺序执行:用 for...of for (const item of arr) { await doSomething(item); }特点:一个完成后再执行下一个,顺序稳定。适合:限流的 API 请求(防止并发过多) 数据库写入(避免竞争) 依赖上一步结果的操作并发执行:用 Promise.all + map await Promise.all( arr.map(async (item) => { await doSomething(item); }) );特点:全部同时启动,等所有完成。适合独立任务批量执行,注意不要把接口打爆。 限制并发数量(每次最多 N 个): async function runConcurrent(arr, fn, concurrency = 5) { const results = []; for (let i = 0; i < arr.length; i += concurrency) { const batch = arr.slice(i, i + concurrency); results.push(...await Promise.all(batch.map(fn))); } return results; }选择参考场景 推荐写法顺序 await,依赖上一步 for...of全部并发,互不依赖 Promise.all + map不需要等待结果 forEach(此时没问题)requestIdleCallback:避免长循环阻塞主线程 如果是大量数据的 CPU 密集计算(如点云、坐标变换),不要在主线程单次执行,而是用 requestIdleCallback 分片处理: function processChunked(data, chunkSize = 1000) { return new Promise((resolve) => { let i = 0; const results = []; function step(deadline) { while (i < data.length && deadline.timeRemaining() > 0) { results.push(expensiveOp(data[i])); i++; } if (i < data.length) { requestIdleCallback(step); } else { resolve(results); } } requestIdleCallback(step); }); }特点:浏览器空闲时处理,不阻塞动画和用户交互。Safari 不支持 requestIdleCallback,可以用 setTimeout(fn, 0) 作为降级方案。 一句话总结 涉及 await 就别用 forEach,用 for...of(顺序)或 Promise.all(并发)。