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

前言

想象一下,你打开一个页面,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 个,每个都跑满带宽,整体完成时间反而更短,用户也不会看到满屏的失败提示。

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

  • 不追求 "瞬间全发" 的冲动,而是追求 "有序高效" 的稳定
  • 给服务器、带宽、浏览器都留足呼吸空间
  • 最终换来的是:页面加载更快、失败率更低、用户体验更好
相关推荐
J船长1 小时前
扫盲烂笔头:A 记录、CNAME 的作用, Nginx的用途,为啥叫反向代理
前端
妙码生花1 小时前
从 PHP 到 AI + Golang,程序员自救转型手记(二十六):icon 组件封装
前端·vue.js·typescript
李明卫杭州1 小时前
Vue2 & Vue3 中 Router `props` 配置详解:彻底摆脱 `$route` 依赖
前端·vue.js·前端框架
掘金一周1 小时前
裁员了,公司很爽快,赔偿一分没少 | 沸点周刊 7.8
前端·人工智能·后端
熊猫钓鱼>_>1 小时前
2026 前端的三场底层革命:当 Vue、React Native 和 Three.js 同时去掉中间层
前端·javascript·vue.js·react native·架构·webgl·three.js
Fox爱分享1 小时前
字节二面:1000瓶酒,有一瓶是毒药,多少只老鼠可以查出来?
算法·面试·程序员
名字还没想好☜1 小时前
React useEffect 依赖数组踩坑:闭包陷阱与清理函数
前端·javascript·react.js·react·useeffect
Fox爱分享1 小时前
字节二面智力题:100只老虎和1只羊关在一起,这只羊会不会被吃?
算法·面试·程序员
宸翰2 小时前
uni-app 设置 Android 底部虚拟导航栏背景色
android·前端·uni-app