前端处理大量并发请求的实用技巧

前端处理大量并发请求的实用技巧

作为一个在前端摸爬滚打几年的开发者,我深知处理大量并发请求时的崩溃瞬间:用户点个按钮,页面卡成PPT;同时发10个请求,浏览器直接罢工;请求失败后不知道怎么重试......这篇就聊聊我在实际项目中踩过的坑,以及总结的能直接用的解决方案


一、先搞懂问题:为什么并发请求会出问题?

浏览器不是永动机,发请求时它会遇到这些限制:

1. 浏览器的"自私"限制

  • 同域名最多同时发6-8个请求(Chrome默认6个):超过的部分会被排队,等你前面的请求完成才能发。
  • TCP队首阻塞:一个请求的数据包丢了,后面的得等它重传,导致后续请求延迟。
  • 内存/CPU不够用:同时发太多请求,浏览器内存可能爆掉,页面卡顿甚至崩溃。

2. 网络的"不靠谱"

  • 高延迟时,请求堆在服务器门口,用户看到"加载中..."半天没反应。
  • 弱网环境下,请求容易超时或失败,用户反复点击,雪上加霜。

3. 业务的"麻烦事"

  • 用户点了"批量删除10条数据",前端得同时发10个删除请求,但服务器可能处理不过来。
  • 实时仪表盘要同时拉取5个指标,有的快有的慢,页面数据忽闪忽闪的。

二、从简单到进阶:我能直接用的解决方案

1. 基础版:限制并发数,别让浏览器"堵车"

问题场景 :用户点了"导出100条数据",前端循环发100个请求,结果浏览器卡死。
解决思路:别一次性发所有请求,控制同时发的数量(比如每次发5个),等前面的完成了再发下一批。

代码实现(简单版并发控制):

typescript 复制代码
// 发请求的函数数组(比如100个删除请求)
const requestFns = Array.from({ length: 100 }, (_, i) => 
  () => fetch(`/api/delete/${i}`, { method: 'DELETE' })
);

// 控制并发数的函数(关键!)
async function runWithConcurrency(tasks, maxConcurrent = 5) {
  const results = [];
  const running = new Set();

  for (const task of tasks) {
    // 如果同时跑的任务没超过上限,直接发
    if (running.size < maxConcurrent) {
      const p = task().finally(() => running.delete(p));
      running.add(p);
      results.push(p);
    } else {
      // 等前面的任务有一个完成,再发新的
      await Promise.race(running);
      // 重新检查队列(这里简化了,实际可能需要递归)
    }
  }

  // 等所有任务完成
  await Promise.all(running);
  return results;
}

// 使用:同时最多发5个请求
runWithConcurrency(requestFns, 5).then(() => {
  console.log('全部导出完成!');
});

注意 :实际项目中,这个函数需要加重试 (比如某个请求失败了,自动再发一次)、超时 (比如等5秒没响应就取消)、进度反馈(告诉用户"已完成30%")。


2. 进阶版:别重复发请求,做个"请求去重"

问题场景 :用户快速点击"刷新数据"按钮,前端发了10次同一个请求,服务器压力大,页面数据乱闪。
解决思路:发请求前先检查"这个请求我发过了吗?",如果正在发或已经发过,就等结果,别重复发。

代码实现(带缓存的请求去重):

typescript 复制代码
// 用Map存正在进行的请求(key是请求的URL+参数)
const requestCache = new Map<string, Promise<any>>();

// 发请求的函数(带去重)
async function fetchWithCache(url: string, params?: any) {
  // 生成唯一的请求key(把参数拼到URL里)
  const key = `${url}?${new URLSearchParams(params).toString()}`;

  // 如果这个请求已经在发了,直接等结果
  if (requestCache.has(key)) {
    return requestCache.get(key);
  }

  // 否则,发请求并缓存Promise
  const promise = fetch(url, { method: 'POST', body: JSON.stringify(params) })
    .then(res => res.json())
    .finally(() => {
      // 请求完成后,从缓存里删掉(避免内存泄漏)
      requestCache.delete(key);
    });

  requestCache.set(key, promise);
  return promise;
}

// 使用:不管点多少次,只会发一次请求
fetchWithCache('/api/data', { page: 1 }).then(data => {
  console.log('数据加载完成', data);
});

升级 :如果需要"过期时间"(比如5分钟内重复请求直接用缓存),可以给requestCache加个时间戳,检查是否过期。


3. 实战版:批量操作的"进可攻退可守"

问题场景 :用户点了"批量删除10条数据",但其中3条删除失败,页面没反应,用户不知道咋回事。
解决思路

  • 发请求时记录每个任务的状态(等待/成功/失败)。
  • 失败的任务可以重试,或者让用户手动重试。
  • 实时更新进度,让用户知道"到哪一步了"。

代码实现(批量操作管理器):

typescript 复制代码
class BatchManager {
  // 存所有任务的状态(id、状态、进度)
  tasks: Array<{
    id: string;
    status: 'waiting' | 'running' | 'success' | 'fail';
    progress?: number;
  }> = [];

  // 最大同时并发数
  maxConcurrent = 3;

  // 添加任务(比如删除10条数据)
  addTasks(taskIds: string[]) {
    // 初始化任务状态
    this.tasks = taskIds.map(id => ({
      id,
      status: 'waiting'
    }));

    // 开始处理任务
    this.processTasks();
  }

  // 处理任务(核心逻辑)
  private async processTasks() {
    // 找出可以跑的任务(状态是waiting,且当前跑的任务没超过上限)
    const waitingTasks = this.tasks.filter(t => t.status === 'waiting');
    const runningCount = this.tasks.filter(t => t.status === 'running').length;

    // 如果没任务要跑,或者跑满了,就等
    if (waitingTasks.length === 0 || runningCount >= this.maxConcurrent) {
      setTimeout(() => this.processTasks(), 500); // 等500ms再检查
      return;
    }

    // 跑最多maxConcurrent个任务
    const toRun = waitingTasks.slice(0, this.maxConcurrent - runningCount);
    toRun.forEach(task => {
      task.status = 'running';
      this.updateUI(); // 更新页面显示

      // 发请求(模拟删除接口)
      fetch(`/api/delete/${task.id}`, { method: 'DELETE' })
        .then(() => {
          task.status = 'success';
        })
        .catch(() => {
          task.status = 'fail';
        })
        .finally(() => {
          this.updateUI(); // 请求完成后更新页面
          this.processTasks(); // 继续处理下一个任务
        });
    });
  }

  // 更新页面(比如显示进度条、颜色标记)
  private updateUI() {
    // 这里可以调用页面的渲染函数,比如:
    // document.getElementById('task-list').innerHTML = this.tasks.map(t => `
    //   <div class="task ${t.status}">${t.id}</div>
    // `).join('');
  }
}

// 使用:添加10个删除任务
const manager = new BatchManager();
manager.addTasks(['task1', 'task2', ..., 'task10']);

扩展

  • 失败的任务可以加"重试"按钮,点击后重新加入任务队列。
  • 进度条可以用task.progress来更新(比如上传文件时,显示已传百分比)。

4. 终极版:用WebSocket"实时唠嗑"

问题场景 :实时仪表盘要每3秒拉一次5个指标,每次发5个请求,页面数据忽闪,还容易超时。
解决思路:用WebSocket和服务器"保持聊天",服务器有新数据时主动推过来,不用前端反复发请求。

代码实现(WebSocket连接管理):

typescript 复制代码
class RealTimeData {
  private socket: WebSocket | null = null;
  // 存每个指标的最新数据
  data: Record<string, any> = {};

  // 连接服务器
  connect(url: string) {
    this.socket = new WebSocket(url);

    // 连接成功后,告诉服务器我要哪些指标
    this.socket.onopen = () => {
      this.socket.send(JSON.stringify({
        type: 'subscribe',
        metrics: ['cpu', 'memory', 'network', 'disk', 'users']
      }));
    };

    // 收到服务器推送的数据
    this.socket.onmessage = (event) => {
      const { metric, value } = JSON.parse(event.data);
      this.data[metric] = value;
      this.updateUI(); // 更新页面
    };

    // 连接断了,自动重连
    this.socket.onclose = () => {
      console.log('连接断了,5秒后重连...');
      setTimeout(() => this.connect(url), 5000);
    };
  }

  // 页面更新(比如渲染图表)
  private updateUI() {
    // 这里可以调用ECharts等库渲染数据
    console.log('数据更新', this.data);
  }
}

// 使用:连接到服务器,实时获取数据
const realTime = new RealTimeData();
realTime.connect('wss://your-server.com/ws');

注意:WebSocket适合"实时性高、数据变化频繁"的场景(比如聊天、监控),如果是"一次性拉取大量数据",还是HTTP更合适。


三、避坑指南:这些坑我踩过!

  1. 别滥用并发控制:并发数不是越小越好,太小会导致请求变慢(比如同时发2个,不如发5个快)。根据浏览器限制(6-8)和服务端承受能力调。
  2. 请求去重要加过期时间:不然用户刷新页面后,旧的缓存请求可能还在,导致数据过时。
  3. 失败重试别无脑重试:比如用户删除数据,第一次失败可能是权限问题,重试10次也没用,应该提示用户"删除失败,请检查权限"。
  4. 别让请求"堵死"页面:如果请求卡住了,页面应该给提示(比如"网络开小差了,点击重试"),而不是干等。

总结

处理大量并发请求,核心就3件事:

  1. 控制节奏:别让浏览器/服务器"堵车",用并发控制函数限制同时发的数量。
  2. 避免重复:用缓存记录已发的请求,别让用户点一下按钮发10次同样的请求。
  3. 兜底处理:失败了能重试,卡住了能提示,让用户知道"发生了啥"。

这些方案都是我在实际项目中验证过的,直接复制就能用。遇到具体问题(比如弱网环境、超大数据量),再针对性调整就行~

相关推荐
崔庆才丨静觅12 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606113 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了13 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅13 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅13 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅14 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment14 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅14 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊14 小时前
jwt介绍
前端
爱敲代码的小鱼14 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax