并发请求控制:前端网络调度的艺术
场景题:
“假设有一个需求,要求你一次性向后端发起 100 张图片的瞎子啊请求。但浏览器对同域名下的 TCP 连接数有限制(通常 Chrome 是 6 个),如果 100 个请求同时发出去,大量的请求会被挂起,甚至导致整体超时宕机。
请你手写一个并发请求控制器,要求最大并发数 max 为 3,尽可能快地完成这 100 个请求。”
原理解析
并发控制的核心思想类似“线程池”或“排队叫号机制”:
- 我们始终维持一个“正在执行的请求队列”或一个计数器,其上限必定为
max。 - 一开始,我们拉起
max个任务同时起跑。 - 关键点:只要有一个任务跑完了(Resolved 或 Rejected),就立刻把它从执行队列里踢出去,并从剩下的待执行任务里抓一个新的补上去。
- 如此循环往复,直到所有任务全部执行完毕。
经典手写实现(Promise 控制)
面试中,最优雅的解法是利用 Promise 和异步递归来实现:
面试官追问
Q1:如何优雅地控制大文件上传时的并发?
答:和上述原理完全一致。在大文件切片上传中,经常会切出几百个 5MB 的 Chunk,我们必须用并发控制套在 Axios 外部。如果有其中一个切片通过上述的 Catch 捕获到了失败,通常我们会引入“重试机制(Retry)”,重试 3 次仍失败则触发外部的 AbortController 掐死所有剩下的并发请求,提示上传失败。
Q2:如果用 ES9 的异步迭代器(for await...of)怎么写?
答:可以使用业界著名的被广泛使用的三方库 p-limit 的精简原理来实现:
本质是维护一个队列 queue,一旦达到最大并发生,新的任务进不来会一直处于 await new Promise(...) 挂起状态。直到前面有人完成并在 finally 中调用了 next 放行函数。

