前端开发中大规模并发请求解决方案最佳实践

文章目录

前端大规模并发请求,指的是短时间内同时发起数十 / 上百个接口请求,直接并发会导致:浏览器请求阻塞(同域名最多 6-8 个并发)、服务器压力过载、页面卡顿、请求超时 / 失败。

核心解决思路:控制并发数 + 任务队列调度 + 缓存 + 防抖 / 节流 + 合并请求。

限制并发数

浏览器对同一个域名默认限制 6~8 个并发请求,超出的请求会进入排队等待,导致整体请求耗时剧增。

最佳实践:手动控制并发数(比如 3~5 个),用任务队列依次执行,避免浏览器阻塞。

使用p-limit库

p-limit ‌ 是一个轻量级 JavaScript 库,主要用于‌控制 Promise 或异步函数的并发执行数量 ‌,防止系统因过载而崩溃,安装只需运行 npm install p-limit 命令 。‌‌‌

🚀 基础用法与核心 API

  1. 创建限制器 ‌:引入库后调用 pLimit 函数并传入最大并发数,返回一个限制器函数。
    • 基础用法:const limit = pLimit(3) 表示最多同时执行 3 个任务 。
    • 高级配置:支持传入对象参数,如 {concurrency: 5, rejectOnClear: true},决定清空队列时是否拒绝待处理任务 。‌‌‌
  2. 执行受限任务 ‌:使用限制器包装异步函数,配合 Promise.all 等待所有任务完成。
    • 包装任务:limit(() => fetchData('user1')),函数不会立即执行,而是进入队列 。
    • 批量处理:使用 limit.map(iterable, mapper) 可更简洁地处理数组等可迭代对象,自动映射为限流任务 。‌‌‌
  3. 状态监控与控制 ‌:限制器实例暴露了多个属性用于实时监控和调整。
    • activeCount‌:获取当前正在执行的任务数量 。
    • pendingCount‌:获取排队等待的任务数量 。
    • ‌**clearQueue()**‌:清空未执行的队列任务,但不会取消正在运行的任务 。
    • 动态调整 ‌:部分版本支持直接修改 limit.concurrency 动态调整并发上限 。‌‌‌

🛠️ 常见应用场景

  1. API 请求限流 ‌:调用外部接口时限制并发数,避免触发速率限制(Rate Limit)或导致服务器压力过大。
    • 示例:设置 pLimit(3) 确保每秒最多发起 3 个请求,配合 limit.map 批量获取用户数据 。‌‌‌
  2. 批量文件处理 ‌:读写大量文件时限制同时打开的文件句柄数量,防止资源耗尽。
    • 示例:处理上传目录文件时,限制同时读取 5 个文件内容,解析完成后释放资源 。‌‌‌
  3. 任务优先级控制 ‌:通过创建多个不同并发级别的限制器,实现核心任务优先执行。
    • 示例:高优先级任务使用 pLimit(3),后台低优先级任务使用 pLimit(1),确保关键业务不受阻塞 。‌‌‌

示例代码

javascript 复制代码
import pLimit from 'p-limit'

initData();

const initData = () => {
    console.log('p-limit')

    const limit = pLimit(2); // 设置最大并发数量为 8

    const input = [ // 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)
    })
};

const fetchSomething = (str) => {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log(str)
            resolve(str)
        }, 1000)
    })
}

效果如图

自己封装方法

请求队列封装

纯 JS 封装,axios/fetch 通用,支持限制最大并发数、任务排队、暂停、清空队列、重试

严格控制并发:同一时间最多 maxLimit 个请求

自动排队:超出并发自动进入等待队列

失败重试:可配置重试次数 + 间隔

灵活管控:暂停 / 恢复 / 清空

无侵入:不改动原有请求逻辑,只包一层函数

RequestQueue.js

javascript 复制代码
class RequestQueue {
    /**
     * @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) {
        return new Promise((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++;
        const item = this.waitQueue.shift();

        const execute = () => {
            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 = [];
    }

}

export default RequestQueue

index.vue

javascript 复制代码
<template>
    <div class="queue">
        <div class="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>
import RequestQueue from '@/utils/RequestQueue'

// 模拟异步请求
const fetchSomething = (id) => {
    // 关键:必须返回函数,让队列控制什么时候执行
    return () => new Promise((resolve) => {
        setTimeout(() => {
            console.log('执行任务:', id)
            resolve(id)
        }, 1000)
    })
}

// 初始化队列:最大并发3,重试1次,间隔300ms
const requestQueue = new RequestQueue(3, 1, 300)

// 批量添加 10 个任务
const addTask = () => {
    const taskList = [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)
            })
    })
}


// 队列操作方法
const pause = () => requestQueue.pause()
const resume = () => requestQueue.resume()
const clearWaitQueue = () => requestQueue.clearWaitQueue()

</script>

<style scoped>
.queue {
    font-size: 16px;
    padding: 20px;
}
.title {
    margin-bottom: 20px;
}
</style>

效果如图

合并请求:减少请求数量

能发 1 个请求就绝不发 10 个,从源头降低并发。

后端配合实现 批量接口

前端把多个参数打包成数组,后端一次性返回:

javascript 复制代码
// 不推荐
GET /api/user/1
GET /api/user/2
GET /api/user/3

// 推荐(批量)
GET /api/user?ids=1,2,3

缓存策略:避免重复请求

把相同的请求缓存起来,下次再请求时,直接从缓存里取。

javascript 复制代码
const cache = new Map();

axios.interceptors.request.use(config => {
  const key = config.url + JSON.stringify(config.params);
  if (cache.has(key)) {
    return Promise.reject(`已缓存:${key}`); // 取消重复请求
  }
  cache.set(key, true);
  return config;
});

防抖 / 节流(针对用户操作触发的请求)

搜索框:用防抖(debounce),停止输入 500ms 再发请求

滚动加载:用节流(throttle),固定时间只发一次请求

懒加载 / 分页加载

只加载可视区域数据,不一次性请求所有数据

相关推荐
下次再写10 天前
【Redis实战】深入理解Redis缓存策略:从原理到Spring Boot实践
java·spring boot·redis·缓存穿透·缓存击穿·分布式缓存·缓存策略
似水流年QC6 个月前
深入 Pinia 工作原理:响应式核心、持久化机制与缓存策略
缓存·pinia·持久化·缓存策略
在未来等你10 个月前
RAG实战指南 Day 28:RAG系统缓存与性能优化
性能优化·信息检索·缓存策略·llm应用·rag系统
morris1311 年前
【redis】CacheAside的数据不一致性问题
redis·缓存策略·cache aside·数据不一致性
南客先生1 年前
5G融合消息PaaS项目深度解析 - Java架构师面试实战
java·微服务·高并发·paas·分布式系统·缓存策略·5g融合消息
码农研究僧1 年前
Redis 中 TTL 的基本知识与禁用缓存键的实现策略(Java)
java·redis·缓存·缓存策略
开源架构师1 年前
开源架构的性能优化:极致突破,引领卓越
性能优化·架构·开源·代码优化·数据库优化·异步处理·缓存策略
Amd7942 年前
Django性能优化:提升加载速度
http请求·dns查询·前端优化·缓存策略·浏览器缓存·服务器响应·cdn分发
Amd7942 年前
探索图片与Base64编码的优势与局限性
http请求优化·图片base64·缓存策略·加载性能优化·数据安全性·文件管理简化·编码转换