核心问题是循环中 await 导致请求串行化,而非 await 本身慢;若请求无依赖、无频率限制、无需顺序错误处理,应改用 promise.all 等并行方案,并可通过 p-limit 或 semaphore 控制并发数。

核心问题不是“await 本身慢”,而是循环中每个 await 都在等前一个完成,把本可并行的请求硬生生压成排队执行——总耗时等于所有请求耗时之和,而非最大值。
先判断:你真需要串行吗?
很多场景下,开发者默认写 for...of + await,其实只是因为“习惯”或“怕乱序”,但并未真正需要串行。先问自己三个问题:
- 这次请求的结果,是否被下一个请求当作参数(比如 ID、token、状态)?
- 接口是否有严格调用频率限制(如每秒最多 1 次)?
- 是否必须按顺序处理错误(比如前一个失败,后一个就跳过)?
如果三个答案都是“否”,那大概率该改成并行。
独立任务:直接上 Promise.all 或等效方案
所有请求互不依赖,只关心最终全部结果,这是最典型的优化入口:
- JavaScript:
const results = await Promise.all(items.map(item => api(item))); - Python:
results = await asyncio.gather(*[api(item) for item in items]) - C#:
var results = await Task.WhenAll(items.Select(item => ApiAsync(item)))
注意两点:一是提前创建所有 Promise/Task(别在 map 或 gather 里再 await),二是若任一失败会中断整体,可用 Promise.allSettled 或 asyncio.gather(return_exceptions=True) 容错。
需控速或防爆:加并发数限制
全量并发可能触发浏览器连接上限(通常 6~10 个)、服务端限流或内存暴涨。这时不能放弃并行,而是“有节制地并行”:
- JS 推荐用
p-limit库:const limit = pLimit(3); const results = await Promise.all(items.map(item => limit(() => api(item)))); - Python 可用
asyncio.Semaphore包裹单个请求;C# 可用SemaphoreSlim控制并发数 - 关键点:限制的是“同时发起的请求数”,不是“总请求数”,不影响总吞吐量,只防资源挤兑
真要串行?那就明确用 for...of,别假装并发
如果确实需要串行(比如依赖链、限频、调试友好),反而不该绕弯子。用 for...of 是最清晰、最可控的方式:
- 它天然支持
await,每轮都等完再进下一轮 - 错误能精准定位到某次迭代,
try/catch可套在循环内单独处理 - 千万别混用:
items.forEach(async item => await api(item))是典型伪串行——实际并发发出,又无法统一收口,还难捕错
不复杂但容易忽略











