前端面试题:如何控制批量接口请求的最大并发数
前端面试题如何控制批量接口请求的最大并发数 题目一次性需要发送 100 条接口请求不能全部并发会压垮后端也不能完全串行太慢如何控制最大并发数请手写一个并发控制器。 核心思路一句话用任务队列 运行计数器 完成即补位的并发池模型将并发数锁定在阈值内兼顾吞吐量与服务端稳定性。 主要矛盾 vs 次要矛盾层级矛盾说明主要矛盾前端吞吐量 vs 后端承载上限并发过高→网关限流/502并发过低→用户等待过长次要矛盾①结果顺序 vs 完成顺序并发执行完成顺序不确定业务可能需要按原始顺序聚合次要矛盾②失败处理 vs 整体流程单条失败不应阻塞其余任务但需可追溯次要矛盾③可中断性 vs 已发出请求用户取消时已 in-flight 的请求无法真正撤回只能丢弃响应 三种方案对比┌─────────────────────────────────────────────────────────────────┐ │ 方案APromise.all 全并发 │ │ 100个请求同时发出 → 瞬间打满网关 → 触发限流/超时 → ❌ 不可用 │ ├─────────────────────────────────────────────────────────────────┤ │ 方案Bfor await 串行 │ │ 一个完成再发下一个 → 100×RTT → 速度极慢 → ❌ 浪费网络空闲 │ ├─────────────────────────────────────────────────────────────────┤ │ 方案C并发控制池限流池 ✅ 最优解 │ │ 固定 N 个并发槽位完成一个补一个 → 效率 稳定性兼得 │ └─────────────────────────────────────────────────────────────────┘维度Promise.all串行 await并发池并发数100不可控1N可配置总耗时≈1×RTT理想≈100×RTT≈⌈100/N⌉×RTT服务端压力极高易崩极低可控适用场景≤5个无依赖请求强顺序依赖批量操作推荐️ 并发池架构图文本版┌──────────────────────────────┐ │ 任务队列 (Queue) │ │ [task5, task6, ... task100] │ └──────────┬───────────────────┘ │ shift() 取队首 ▼ ┌─────────────────────────────────────────────────────┐ │ 并发调度器 (Scheduler) │ │ │ │ runningCount: 当前运行数 maxConcurrency: N │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Slot 1 │ │ Slot 2 │ │ Slot N │ ← N个槽位 │ │ │ task1 ▶ │ │ task2 ▶ │ │ task3 ▶ │ │ │ └────┬────┘ └────┬────┘ └────┬────┘ │ │ │done │done │done │ │ ▼ ▼ ▼ │ │ runningCount-- → 从队列取新任务 → runningCount │ └─────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────┐ │ 结果收集 (按原始index存放) │ │ results[0..99] │ └──────────────────────────────┘ 终止条件queue.length 0 runningCount 0 核心流程文本流程图开始 │ ├─ 1. 将 100 个请求封装为 () Promise 函数推入 taskQueue │ ├─ 2. 初始化循环 min(maxConcurrency, queue.length) 次 │ 调用 runNext() 启动首批任务 │ ├─ 3. runNext() 内部 │ ├─ runningCount │ ├─ 执行 task() │ ├─ .then → 收集结果到 results[index] │ ├─ .catch → 记录错误 / 触发重试 │ └─ .finally → │ ├─ runningCount-- │ ├─ 队列非空→ runNext()补位 │ └─ 队列空 runningCount0→ resolve 整体 Promise │ └─ 4. 返回 Promiseresults[]调用方 await 获取全部结果 完整示例代码生产级classConcurrencyScheduler{constructor({maxConcurrency5,retries0,retryDelay1000}{}){this.maxmaxConcurrency;this.retriesretries;this.retryDelayretryDelay;this.queue[];// 等待队列this.running0;// 当前运行数this.results[];// 按原始顺序收集结果this.errors[];// 错误收集this.isPausedfalse;// 中断标志this._resolvenull;}/** 添加任务task 是返回 Promise 的函数index 用于结果排序 */addTask(task,index){this.queue.push({task,index});}/** 启动调度 */start(){returnnewPromise((resolve){this._resolveresolve;constinitialCountMath.min(this.max,this.queue.length);for(leti0;iinitialCount;i){this._runNext();}// 边界队列为空0个任务if(this.queue.length0this.running0){resolve(this.results);}});}/** 中断清空队列不再补位已发出的请求无法撤回但结果会被丢弃 */abort(){this.isPausedtrue;this.queue[];}async_runNext(){if(this.isPaused||this.queue.length0){this._checkDone();return;}const{task,index}this.queue.shift();this.running;try{constresultawaitthis._execWithRetry(task);this.results[index]{status:fulfilled,value:result};}catch(err){this.results[index]{status:rejected,reason:err};this.errors.push({index,error:err});}finally{this.running--;this._runNext();// 补位}}/** 带重试的执行 */async_execWithRetry(task,attempt0){try{returnawaittask();}catch(err){if(attemptthis.retries){awaitthis._sleep(this.retryDelay*(attempt1));// 递增延迟returnthis._execWithRetry(task,attempt1);}throwerr;}}_checkDone(){if(this.queue.length0this.running0this._resolve){this._resolve(this.results);}}_sleep(ms){returnnewPromise((r)setTimeout(r,ms));}}使用示例// 模拟 100 个接口请求consturlsArray.from({length:100},(_,i)/api/data/${i});constschedulernewConcurrencyScheduler({maxConcurrency:6,// 最大 6 个并发retries:2,// 失败最多重试 2 次retryDelay:1000,});urls.forEach((url,index){scheduler.addTask(()fetch(url).then(resres.json()),index// 保证结果按原始顺序);});constresultsawaitscheduler.start();console.log(results);// [{status:fulfilled, value:...}, ...] 长度100顺序一致中断示例// 用户点击取消按钮cancelBtn.onclick()scheduler.abort(); 使用场景场景说明推荐并发数批量导入Excel→逐条调接口几千条数据逐条提交5~10批量查询详情列表页补充信息100个ID查详情6~8批量文件上传分片大文件切片并发上传3~6受带宽限制图片批量压缩/预览前端本地处理 上传4~6爬虫/数据采集合规前提下控制对目标站点压力2~4⚠️ 边界场景 注意事项边界处理方式任务数为 0start() 直接 resolve([])任务数 maxConcurrency全部立即执行无需排队单个任务永远 pending加超时包装Promise.race([task(), timeout(ms)])全部失败收集 errors不阻塞整体流程最终统一上报内存溢出10万任务分批 push 入队 / 配合 Web Worker / 后端分片接口浏览器同域并发限制HTTP/1.1 同域最多 6 连接HTTP/2 多路复用无此限制AbortController 真正取消配合 fetch 的 signal 参数abort 时真正断开连接超时包装补充functionwithTimeout(promiseFn,ms10000){return()Promise.race([promiseFn(),newPromise((_,reject)setTimeout(()reject(newError(TIMEOUT)),ms)),]);}scheduler.addTask(withTimeout(()fetch(url),8000),index); 与相关 API 的关系补充知识点API作用局限Promise.all全并发全部成功才 resolve无法限流一个失败全挂Promise.allSettled全并发收集所有结果同样无法限流Promise.any全并发一个成功即 resolve场景不同p-limitnpm现成并发控制库原理同本文生产可直接用p-queuenpm支持优先级、暂停、事件更重适合复杂调度面试手写考察的是原理理解生产中可用p-limit一行搞定importpLimitfromp-limit;constlimitpLimit(6);constresultsawaitPromise.all(urls.map((url,i)limit(()fetch(url)))); 面试满分答案话术版面对大批量接口请求我采用并发池限流方案设定最大并发阈值如 6将所有请求封装为返回 Promise 的函数存入任务队列初始启动 N 个任务占满并发槽位任一任务完成后计数器减一立即从队列头部取出新任务补位计数器加一队列为空且运行数为零时整体完成按原始顺序返回结果数组。这样做既避免瞬时并发打崩网关又比串行高效数倍。在此基础上我还做了三层扩展失败重试单任务失败自动重试 N 次递增延迟重试耗尽才标记失败中断取消暴露 abort 方法清空队列、停止补位配合 AbortController 真正取消 in-flight 请求超时兜底用 Promise.race 包装防止单任务 hang 住占满槽位。如果数据量特别大上万条还会结合后端分页/分片接口从源头减少请求数前端再配合并发池双管齐下。以上即本题完整核心干货掌握队列 计数器 补位三要素即可应对所有变体。

相关新闻

两次LLM调用反而更省钱?信息提取的两阶段设计实战

两次LLM调用反而更省钱?信息提取的两阶段设计实战

在做 LLM 信息抽取类应用时,一个很现实的问题摆在面前:想让准确率更高,往往要堆更大的模型、更长的上下文、更多的 few-shot 示例,结果就是成本水涨船高;反过来想省钱,模型又频繁漏字段、输出幻觉内容。 最…

2026/10/27 21:00:10 阅读更多 →
上下文工程与LLM Harness实战:构建可复用的LLM应用运行框架

上下文工程与LLM Harness实战:构建可复用的LLM应用运行框架

最近在项目里做 LLM 应用落地时,我最大的体会是:模型能力本身已经不是唯一瓶颈,真正影响效果的反而是“喂给模型的上下文怎么组织”。同样一个模型,上下文拼得好,输出稳定且贴近业务;上下文一塌糊涂&#x…

2026/10/24 11:03:29 阅读更多 →
MagicOS 11双架构解析:美学升维与流畅突破的系统级体验

MagicOS 11双架构解析:美学升维与流畅突破的系统级体验

MagicOS 11 这次最值得关注的,不是壁纸库又多了几张图,而是把“美学升维”和“流畅突破”放到同一个双架构体系里来做。我平时看系统更新,最反感两种稿子:一种把所有改动都形容成颠覆,另一种只发参数表不解释实际影响。…

2026/10/24 0:37:32 阅读更多 →

最新新闻

Electron桌面应用开发实战:从环境搭建到打包发布全流程

Electron桌面应用开发实战:从环境搭建到打包发布全流程

做前端这些年,我大部分时间都在跟浏览器标签页打交道,直到第一次把桌面应用跑起来,才发现原来自己一直被限制在地址栏和刷新按钮围起来的框框里。Electron这个词,最开始是在某段时间频繁出现在同事的讨论里,一个用HTML…

2026/10/27 17:33:32 阅读更多 →
滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

滑盖智能锁T109深度解析:结构原理、安装调试与故障排查

简介:亚太天能T109滑盖智能锁产品说明书是一份PDF格式的官方技术文档,主要面向智能锁安装人员、终端用户及售后维护者,帮助快速掌握产品安装、配置与常见故障处理。全包仅1个PDF文件,大小4.37MB,内容结构清晰&#xff…

2026/10/27 4:06:49 阅读更多 →
PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

PMSM弱磁控制原理与Simulink建模:高速AGV驱动系统设计指南

上个月我在调试一台高速AGV的驱动系统,整车要求空载跑到8m/s,电机转速已经拉到额定点,驱动器的输出电压也顶到了母线电压极限附近。再往上踩速度指令,转速纹丝不动,三相电流反而开始震荡。那会儿我的第一反应就是&…

2026/10/27 7:21:53 阅读更多 →
C++核心知识点:从内存管理到STL的实战指南

C++核心知识点:从内存管理到STL的实战指南

C 这门语言我前前后后折腾了十几年,用它写过嵌入式驱动、做过游戏服务端,也搭过基础组件库。每次看到新人一头扎进"C 核心知识点"这个搜索词,我其实挺感慨的——这门语言的难点从来不是语法多,而是知识点之间的耦合太深…

2026/10/27 18:20:50 阅读更多 →
T109滑盖智能锁怎么装?安装调试与故障排查全攻略

T109滑盖智能锁怎么装?安装调试与故障排查全攻略

简介:亚太天能T109滑盖智能锁产品说明书以PDF格式发布,适合智能锁用户、安装人员与售后维修人员阅读。内容围绕T109系列指纹密码刷卡锁展开,先介绍前面板、后面板、锁体、机械钥匙、电池盒盖等部件名称与功能,再给出完整安装步骤&…

2026/10/27 6:20:47 阅读更多 →
LLM知识降熵:从信息垄断到理解权下放

LLM知识降熵:从信息垄断到理解权下放

你有没有过这种经历:翻开源码注释、论文摘要或者一份专业报告,每个字都认识,连在一起却像天书。想找人聊聊,身边要么没人懂,要么懂的懒得跟你解释。后来我试着把同一段内容丢给一个大语言模型,几秒钟之后&a…

2026/10/27 4:06:50 阅读更多 →

日新闻

周新闻

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 13:55:19 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 7:21:50 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/27 19:19:37 阅读更多 →