主题
forEach 支持异步函数吗?
forEach 本身不会等待回调返回的 Promise,因此不能用于保证按序或等待异步回调完成。
为什么?
forEach 只是同步地对数组中每一项调用回调,并忽略回调的返回值。即使回调是 async 并返回 Promise,forEach 不会使用或等待这些 Promise,所以外层不会被阻塞。
示例(不会等待):
js
const arr = [1, 2, 3]
arr.forEach(async (n) => {
await new Promise((r) => setTimeout(r, 1000))
console.log(n)
})
console.log('done') // 很可能比所有 console.log(n) 先输出常见替代方案
并行(同时触发所有异步) — map + Promise.all
如果希望并行执行所有任务并在全部完成后继续:
js
await Promise.all(
arr.map(async (n) => {
await doAsync(n)
console.log(n)
}),
)
console.log('并行结束')提示
Promise.all 在任意一个 Promise reject 时会立刻 reject(短路)。如果需要等到所有任务完成并且想收集每个任务的状态,使用 Promise.allSettled:
js
const results = await Promise.allSettled(arr.map((n) => doAsync(n)))
results.forEach((r, i) => {
if (r.status === 'fulfilled') console.log(i, 'ok', r.value)
else console.error(i, 'failed', r.reason)
})串行(按顺序等待) — for...of + await
当每项需要按顺序执行或下一项依赖上一项结果时,使用 for...of:
js
for (const n of arr) {
await doAsync(n)
console.log(n)
}
console.log('顺序结束')提示
另一种较少见但可用的串行写法是使用 reduce:
js
await arr.reduce((p, n) => p.then(() => doAsync(n).then(() => console.log(n))), Promise.resolve())通常更推荐使用 for...of,因为可读性更好。
并发限流(限制同时运行的异步数量)
当数组很大且不希望一次性并发过多请求时,需要限流。
js
import pLimit from 'p-limit'
const limit = pLimit(5) // 最多同时 5 个并发
await Promise.all(arr.map((n) => limit(() => doAsync(n))))js
async function parallelWithLimit(items, limit, iterator) {
const results = []
const executing = []
for (const item of items) {
const p = Promise.resolve().then(() => iterator(item))
results.push(p)
const e = p.then(() => executing.splice(executing.indexOf(e), 1))
executing.push(e)
if (executing.length >= limit) await Promise.race(executing)
}
return Promise.all(results)
}
await parallelWithLimit(arr, 5, doAsync)