前几天写脚本,想在数组遍历里挨个 await 一下:
const arr = [1, 2, 3];
arr.forEach(async (item) => {
await sleep(1000);
console.log(item);
});
console.log('结束');
预期看到 1 2 3 结束,结果输出是:
结束
1
2
3
forEach 完全没等异步回调结束,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 就会消失。
