🚦 前端并发请求 “交通管制”:别让你的接口堵成早高峰

前言

想象一下,你打开一个页面,6 个请求同时冲向服务器,就像 6 辆车挤在单行道上 ------ 有的爆胎(超时失败) ,有的龟速(加载缓慢),用户看着转圈的加载动画,心态直接崩了。

一、为什么要给请求 "限速"?

上了这么多年网,想必大家应该都经历过以下画面:

  • 打开电商详情页,一堆商品图片、评论、推荐接口同时发起,页面半天加载不出来
  • 某个大文件上传时,其他小请求直接超时失败
  • 浏览器控制台里一片红色的 net::ERR_CONNECTION_RESET

这本质上就是并发请求过多,带宽被 "挤爆" ,就像早高峰的地铁,大家都想上车,结果谁也动不了。

前端控制并发,就是给这些请求当 "交通警察":

  • 规定同一时间最多只能有 N 辆车(请求)在路上
  • 剩下的车乖乖在停车场(任务队列)排队
  • 等路上的车到站(请求完成),再放一辆进去

这样既能避免服务器和带宽过载,又能保证所有请求最终都能被处理。

二、先造个 "模拟请求":让请求有快有慢

我们先写一个模拟的异步请求函数 ajax,用 setTimeout 模拟网络延迟,超过 5000ms 就直接 "请求失败":

JavaScript 复制代码
function ajax(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            if(time > 5000) {
                reject('请求失败')
            } else {
                resolve('请求成功')
            }
        }, time)
    })
}

三、核心工具:并发控制器 Limit 类

接下来就是我们的主角 ------并发控制器 Limit,它会帮我们管理所有请求的 "上路权限":

JavaScript 复制代码
class Limit {
    constructor(parallCount = 2) {
        this.tasks = [];       // 任务队列:存还没上路的请求
        this.runningCount = 0; // 计数器:当前正在跑的请求数
        this.parallCount = parallCount; // 最大并发数:默认最多2个请求同时跑
    }

    // 添加任务到队列
    add(task) {
        return new Promise((resolve, reject) => {
            this.tasks.push({
                task,
                resolve,
                reject
            });
            this._run(); // 尝试启动任务
        });
    }

    // 核心调度器:控制什么时候放新任务上路
    _run() {
        if (this.runningCount < this.parallCount && this.tasks.length) {
            const { task, resolve, reject } = this.tasks.shift(); // 从队列头取一个任务
            this.runningCount++; // 上路了,计数器+1

            task()
                .then(resolve, reject)
                .finally(() => {
                    this.runningCount--; // 任务完成/失败,计数器-1
                    this._run(); // 继续调度下一个任务
                });
        }
    }
}

代码拆解:

  1. 构造函数 :默认最大并发数 2,你可以改成 3、5 甚至 10,看业务需求
  2. add 方法:把请求包装成任务塞进队列,然后立刻尝试启动调度
  3. _run 方法 (私有调度器):
    • 检查:当前正在跑的请求数 < 最大并发数,且队列里还有任务
    • 满足条件:从队列里取第一个任务,计数器 + 1,执行任务
    • 任务结束(无论成功 / 失败):计数器 - 1,再次调用 _run 放新任务上路

四、实战演示:给 6 个请求 "限流"

我们创建一个并发控制器,然后一次性加 6 个任务,看看它们是怎么排队执行的:

JavaScript 复制代码
// 初始化控制器:最多同时 2 个请求
let limit = new Limit(2);

// 封装添加任务的函数
function addTask(time, index) {
  limit
    .add(() => ajax(time))
    .then(() => {
      console.log(`任务${index}完成`);
    })
    .catch(() => {
      console.log(`任务${index}失败`);
    });
}

// 一次性添加6个任务
addTask(10000, 1); // 耗时10s → 失败
addTask(4000, 2);  // 耗时4s → 成功
addTask(8000, 3);  // 耗时8s → 失败
addTask(1000, 4);  // 耗时1s → 成功
addTask(5000, 5);  // 耗时5s → 成功
addTask(2000, 6);  // 耗时2s → 成功

执行流程(脑补一下):

  1. 0s 时 :任务 1、任务 2 先上路,runningCount = 2,剩下任务 3-6 在队列排队
  2. 4s 时:任务 2 完成,计数器变回 1 → 立刻放任务 3 上路,现在跑任务 1、3
  3. 8s 时:任务 1 失败,计数器变回 1 → 放任务 4 上路,现在跑任务 3、4
  4. 9s 时:任务 4 完成,计数器变回 1 → 放任务 5 上路,现在跑任务 3、5
  5. 10s 时:任务 3 失败,计数器变回 1 → 放任务 6 上路,现在跑任务 5、6
  6. 11s 时:任务 5 完成,计数器变回 1 → 队列空了,任务 6 继续跑
  7. 12s 时:任务 6 完成,所有任务结束

你看,全程最多只有 2 个请求在同时执行,既不会挤爆带宽,又保证了所有请求都被处理。

结语:并发控制不是 "慢",是 "稳"

很多人会问:"我同时发 6 个请求不是更快吗?"

其实不然 ------ 就像你同时开 10 个下载任务,每个都慢得像蜗牛,不如限制成 2 个,每个都跑满带宽,整体完成时间反而更短,用户也不会看到满屏的失败提示。

前端并发控制,本质上是一种 "以退为进" 的智慧:

  • 不追求 "瞬间全发" 的冲动,而是追求 "有序高效" 的稳定
  • 给服务器、带宽、浏览器都留足呼吸空间
  • 最终换来的是:页面加载更快、失败率更低、用户体验更好
相关推荐
福兮说3 小时前
前端下载大文件,点了按钮半天没反应:fetch + blob 的三个问题和三种替代写法
前端·javascript·性能优化·文件下载
GreenTea3 小时前
我把 Agent 的 while 循环拆掉了:一种你可能没想到的 Agent 架构
前端·后端·架构
科技观察哨4 小时前
六足平台选型与纳米定位系统集成:HEB-640六自由度位移台在半导体光刻对准中的参数边界与国产替代评估
前端·人工智能
niucloud-admin4 小时前
JAVA V6 多商户商城 开发文档——web前端
前端
颜进强4 小时前
16 · NestJS LifecycleEvents 生命周期事件:五个钩子、三条边界,和 rag-server 的优雅停机
前端·后端·ai编程
今年下半年6 小时前
【VUE】整合腾讯地图、自定义区域边界、村委名称及资产统计(放大显示资产点位)
前端·javascript·vue.js
郑州光合科技余经理6 小时前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
我叫黑大帅6 小时前
Go日志库工程选型与逃逸分析评测报告
后端·面试·go
此时不提桶,更待何时7 小时前
05-05-B-对象存储与文件服务面试与生产事故实战
面试·nosql
前端snow7 小时前
ai agent --- DeepAgents 中间件
前端