文章目录
- 限制并发数
- 使用p-limit库
- 自己封装方法
- 合并请求:减少请求数量
- 缓存策略:避免重复请求
- 防抖 / 节流(针对用户操作触发的请求)
- 懒加载 / 分页加载
前端大规模并发请求,指的是短时间内同时发起数十 / 上百个接口请求,直接并发会导致:浏览器请求阻塞(同域名最多 6-8 个并发)、服务器压力过载、页面卡顿、请求超时 / 失败。
核心解决思路:控制并发数 + 任务队列调度 + 缓存 + 防抖 / 节流 + 合并请求。
限制并发数
浏览器对同一个域名默认限制 6~8 个并发请求,超出的请求会进入排队等待,导致整体请求耗时剧增。
最佳实践:手动控制并发数(比如 3~5 个),用任务队列依次执行,避免浏览器阻塞。
使用p-limit库
p-limit 是一个轻量级 JavaScript 库,主要用于控制 Promise 或异步函数的并发执行数量,防止系统因过载而崩溃,安装只需运行npm install p-limit命令 。
🚀 基础用法与核心 API
- 创建限制器:引入库后调用
pLimit函数并传入最大并发数,返回一个限制器函数。- 基础用法:
const limit = pLimit(3)表示最多同时执行 3 个任务 。 - 高级配置:支持传入对象参数,如
{concurrency: 5, rejectOnClear: true},决定清空队列时是否拒绝待处理任务 。
- 基础用法:
- 执行受限任务:使用限制器包装异步函数,配合
Promise.all等待所有任务完成。- 包装任务:
limit(() => fetchData('user1')),函数不会立即执行,而是进入队列 。 - 批量处理:使用
limit.map(iterable, mapper)可更简洁地处理数组等可迭代对象,自动映射为限流任务 。
- 包装任务:
- 状态监控与控制:限制器实例暴露了多个属性用于实时监控和调整。
- activeCount:获取当前正在执行的任务数量 。
- pendingCount:获取排队等待的任务数量 。
- **clearQueue()**:清空未执行的队列任务,但不会取消正在运行的任务 。
- 动态调整:部分版本支持直接修改
limit.concurrency动态调整并发上限 。
🛠️ 常见应用场景
- API 请求限流:调用外部接口时限制并发数,避免触发速率限制(Rate Limit)或导致服务器压力过大。
- 示例:设置
pLimit(3)确保每秒最多发起 3 个请求,配合limit.map批量获取用户数据 。
- 示例:设置
- 批量文件处理:读写大量文件时限制同时打开的文件句柄数量,防止资源耗尽。
- 示例:处理上传目录文件时,限制同时读取 5 个文件内容,解析完成后释放资源 。
- 任务优先级控制:通过创建多个不同并发级别的限制器,实现核心任务优先执行。
- 示例:高优先级任务使用
pLimit(3),后台低优先级任务使用pLimit(1),确保关键业务不受阻塞 。
- 示例:高优先级任务使用
示例代码
importpLimitfrom'p-limit'initData();constinitData=()=>{console.log('p-limit')constlimit=pLimit(2);// 设置最大并发数量为 8constinput=[// Limit函数包装各个请求limit(()=>fetchSomething('1')),limit(()=>fetchSomething('2')),limit(()=>fetchSomething('3')),limit(()=>fetchSomething('4')),limit(()=>fetchSomething('5')),limit(()=>fetchSomething('6')),limit(()=>fetchSomething('7')),limit(()=>fetchSomething('8')),];// 执行请求Promise.all(input).then(res=>{console.log(res)})};constfetchSomething=(str)=>{returnnewPromise((resolve,reject)=>{setTimeout(()=>{console.log(str)resolve(str)},1000)})}效果如图
自己封装方法
请求队列封装
纯 JS 封装,axios/fetch 通用,支持限制最大并发数、任务排队、暂停、清空队列、重试
严格控制并发:同一时间最多 maxLimit 个请求
自动排队:超出并发自动进入等待队列
失败重试:可配置重试次数 + 间隔
灵活管控:暂停 / 恢复 / 清空
无侵入:不改动原有请求逻辑,只包一层函数
RequestQueue.js
classRequestQueue{/** * @param {number} maxLimit 最大并发数 * @param {number} retryTimes 失败重试次数 * @param {number} delay 重试间隔 ms */constructor(maxLimit=5,retryTimes=1,delay=300){this.maxLimit=maxLimit;this.retryTimes=retryTimes;this.delay=delay;// 等待执行队列this.waitQueue=[];// 正在执行数量this.runningCount=0;// 暂停状态this.isPause=false;}// 添加请求任务addTask(task){returnnewPromise((resolve,reject)=>{this.waitQueue.push({task,resolve,reject,retryCount:0});this.run();});}// 执行队列run(){if(this.isPause)return;// 超出并发限制 / 无等待任务 直接返回if(this.runningCount>=this.maxLimit||this.waitQueue.length===0)return;this.runningCount++;constitem=this.waitQueue.shift();constexecute=()=>{item.task().then(res=>{item.resolve(res);this.finishTask();}).catch(err=>{// 失败重试if(item.retryCount<this.retryTimes){item.retryCount++;setTimeout(execute,this.delay);}else{item.reject(err);this.finishTask();}});};execute();}// 任务结束finishTask(){this.runningCount--;// 继续下一个this.run();}// 暂停队列pause(){this.isPause=true;}// 恢复队列resume(){this.isPause=false;this.run();}// 清空等待队列(未执行的全部取消)clearWaitQueue(){this.waitQueue=[];}}exportdefaultRequestQueueindex.vue
<template><divclass="queue"><divclass="title">纯JS封装,axios/fetch 通用,支持限制最大并发数、任务排队、暂停、清空队列、重试</div><el-button type="primary"@click="addTask">添加任务</el-button><el-button type="info"@click="pause">暂停队列</el-button><el-button type="success"@click="resume">恢复队列</el-button><el-button type="danger"@click="clearWaitQueue">清空等待队列</el-button></div></template><script setup>importRequestQueuefrom'@/utils/RequestQueue'// 模拟异步请求constfetchSomething=(id)=>{// 关键:必须返回函数,让队列控制什么时候执行return()=>newPromise((resolve)=>{setTimeout(()=>{console.log('执行任务:',id)resolve(id)},1000)})}// 初始化队列:最大并发3,重试1次,间隔300msconstrequestQueue=newRequestQueue(3,1,300)// 批量添加 10 个任务constaddTask=()=>{consttaskList=[1,2,3,4,5,6,7,8,9,10]taskList.forEach((id)=>{requestQueue.addTask(fetchSomething(id)).then(res=>{console.log('任务完成:',id,'结果:',res)}).catch(err=>{console.error('任务失败:',id,err)})})}// 队列操作方法constpause=()=>requestQueue.pause()constresume=()=>requestQueue.resume()constclearWaitQueue=()=>requestQueue.clearWaitQueue()</script><style scoped>.queue{font-size:16px;padding:20px;}.title{margin-bottom:20px;}</style>效果如图
合并请求:减少请求数量
能发 1 个请求就绝不发 10 个,从源头降低并发。
后端配合实现 批量接口
前端把多个参数打包成数组,后端一次性返回:
// 不推荐GET/api/user/1GET/api/user/2GET/api/user/3// 推荐(批量)GET/api/user?ids=1,2,3缓存策略:避免重复请求
把相同的请求缓存起来,下次再请求时,直接从缓存里取。
constcache=newMap();axios.interceptors.request.use(config=>{constkey=config.url+JSON.stringify(config.params);if(cache.has(key)){returnPromise.reject(`已缓存:${key}`);// 取消重复请求}cache.set(key,true);returnconfig;});防抖 / 节流(针对用户操作触发的请求)
搜索框:用防抖(debounce),停止输入 500ms 再发请求
滚动加载:用节流(throttle),固定时间只发一次请求
懒加载 / 分页加载
只加载可视区域数据,不一次性请求所有数据