前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库await for 循环串行
JavaScript异步与 Promise

for 循环里逐个 await 请求是串行的吗?怎么改成并发?

是串行。循环体里的 await 会让函数停在这一轮,等请求回来才进入下一轮,三个 100 毫秒的请求要花 300 毫秒。改成并发的办法是先把所有请求都发出去拿到 Promise,再用 Promise.all 一起等,总耗时约等于最慢的那一个。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#异步与 Promise#异步编程#并发编程
先看核心答案读代码示例
理解线索

串行和并发差在哪一步

  1. 串行发一个、等一个,下一个请求要等上一个回来才发出
  2. 并发同步把所有请求都发出去,手里拿着一组 Promise 再等
  3. Promise.all全部成功才成功,按传入顺序返回结果,有一个失败就立刻失败

决定串行还是并发的不是 await 写在哪,而是请求在什么时候发起。先把 Promise 都创建好再逐个 await,同样是并发。

核心回答

先记住这个答案

是串行。await 会暂停当前 async 函数,循环也跟着停在这一轮,等 Promise 完成才继续下一轮,所以请求是一个接一个发出去的,总耗时是所有请求耗时之和。要改成并发,就先用 map 把所有请求同步发出去,拿到一组 Promise,再用 await Promise.all(promises) 统一等待,总耗时约等于最慢的那个请求。

  • 循环里 await 是串行,总耗时是各请求之和
  • 并发的关键是先发起,再统一 await
  • Promise.all 结果顺序和传入顺序一致
  • 请求很多时要限制并发数,别一次全发

先看串行到底慢多少

比如详情页要根据三个用户 id 拉用户信息,每个请求耗时 100 毫秒。最顺手的写法是 for...of 里逐个 await。

循环里逐个 awaitJavaScript
const request = (id) => new Promise((resolve) => setTimeout(() => resolve('用户' + id), 100))
const ids = [1, 2, 3]

async function serial() {
  const start = performance.now()
  const users = []
  for (const id of ids) {
    users.push(await request(id)) // 等上一个回来才发下一个
  }
  console.log(users.join(','), '耗时', performance.now() - start)
}
serial()
查看输出与解释
用户1,用户2,用户3 耗时 300

三个请求互相没有依赖,却排着队一个一个发,耗时 300 毫秒。请求数量越多,差距越明显,十个请求就是 1 秒。 原因很直接:request(2) 这行代码要等 await request(1) 结束、进入下一轮循环时才会执行。请求是在调用 request 的那一刻发出的,调用被推迟了,请求自然也被推迟了。

改成并发:先发起,再等待

并发的思路是把「发请求」和「等结果」拆开。发请求是同步的,一口气全发出去;等结果交给 Promise.all。

用 Promise.all 一起等JavaScript
const request = (id) => new Promise((resolve) => setTimeout(() => resolve('用户' + id), 100))
const ids = [1, 2, 3]

async function parallel() {
  const start = performance.now()
  const promises = ids.map((id) => request(id)) // 三个请求同时发出
  const users = await Promise.all(promises) // 只等一次
  console.log(users.join(','), '耗时', performance.now() - start)
}
parallel()
查看输出与解释
用户1,用户2,用户3 耗时 100

耗时从 300 降到 100 毫秒。map 执行完时三个请求都已经在路上了,Promise.all 只是等它们全部回来。结果数组的顺序和 ids 一致,不管谁先回来。

不用 Promise.all 也能并发
只要先创建 Promise 再 await,就是并发。比如 const a = request(1) 和 const b = request(2) 先各写一行,再依次 await a 和 await b,两个请求同样是同时发出的。这说明决定串行还是并发的是发起时机,Promise.all 只是让「统一等待」写起来更方便,并能在失败时尽早返回。

什么时候必须串行,什么时候要限流

不是所有循环都该改成并发。下一个请求依赖上一个的结果时,比如分页接口要用上一页返回的游标拉下一页,只能串行。有顺序要求的写操作,比如按顺序上传分片后再合并,也要串行。

请求很多时控制并发数
一次给几百个 id 拉数据,全部并发会同时打出几百个请求。浏览器对同一个域名的 HTTP/1.1 连接数有上限,多出来的请求会排队,服务端也可能因为瞬时压力返回 429。常见做法有两种。一种是分批,每批 5 个用 Promise.all,一批完成再下一批,缺点是一批里最慢的那个会拖住整批。另一种是并发池,始终保持最多 5 个在跑,完成一个就补一个,吞吐更稳定,p-limit 这类库做的就是这个。
回答前,多想一步

容易答错的地方

以为 Promise.all 会帮你发起请求
Promise.all 只负责等,不负责调用。传给它的必须是已经发出去的 Promise。有人为了「统一管理」写成 Promise.all(ids.map((id) => () => request(id))),传进去的是一组函数,Promise.all 会把函数当普通值原样返回,一个请求都没发。反过来,请求是否并发也和 Promise.all 无关,而是看 map 那一步有没有同步地把请求都调用了。
用 Promise.all 后以为失败的那个不影响其他结果
Promise.all 只要有一个拒绝就立刻拒绝,其他请求成功的结果你一个都拿不到,虽然它们照样发完了。页面上各块数据互不影响时,改用 Promise.allSettled,每一项都会返回 status 为 fulfilled 或 rejected 的结果,再分别处理。
先创建一组 Promise,再在循环里逐个 await
这种写法是并发的,但有个隐患:如果第三个请求比第一个先失败,而循环还停在等第一个,第三个的拒绝暂时没有任何处理,会被报成未处理的拒绝。用 Promise.all 或 Promise.allSettled 就不会有这个问题,它们会立刻给每个 Promise 挂上处理。
试着用自己的话回答

面试官还会怎么问?

能手写一个限制并发数的函数吗?

思路是维护一个正在运行的计数。先启动 limit 个任务,每个任务完成时从队列里取下一个启动,同时把结果按原下标存进结果数组。所有任务完成后返回结果数组。关键点是结果要按下标存,不能按完成顺序 push。

串行场景除了 for...of,还有什么写法?

可以用 reduce 把任务串成一条 Promise 链,每一项都挂在上一项的 then 后面。效果和 for...of 加 await 一样,只是可读性差一些,现在基本只在老代码里见到。

并发请求里怎么做到一个失败就取消其他的?

Promise.all 本身不会取消任何请求。可以让所有请求共用一个 AbortController 的 signal,在 catch 里调用 controller.abort(),还没完成的请求就会被中断。

从一道题,走向一组知识

把知识连起来

异步与 Promise

为什么说 async/await 是 Promise 的语法糖?能把它改写成 then 链吗?

把循环里的 await 翻译成 then 链,就能看出每一轮都挂在上一轮后面,串行是必然结果。

异步与 Promise

async/await 里哪些错误该用 try/catch 接,哪些不该接?

改成 Promise.all 并发后,try/catch 包在哪、一个失败会不会吞掉其他结果,是紧接着要处理的问题。

参考资料

  • JavaScript:异步与 Promise参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 先看串行到底慢多少
  3. 改成并发:先发起,再等待
  4. 什么时候必须串行,什么时候要限流
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑