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

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

作为一个在前端摸爬滚打几年的开发者,我深知处理大量并发请求时的崩溃瞬间:用户点个按钮,页面卡成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. 兜底处理:失败了能重试,卡住了能提示,让用户知道"发生了啥"。

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

相关推荐
Yana.nice2 分钟前
sysctl优先级顺序
服务器·前端·网络
米花丶6 分钟前
异步加载弹出层动画丢失问题
前端
小桥风满袖8 分钟前
Three.js-硬要自学系列31之专项学习动画混合
前端·css·three.js
Lanqing_076010 分钟前
淘宝商品详情图API接口返回参数说明
java·服务器·前端·api·电商
karshey20 分钟前
【Element Plus】Menu组件:url访问页面时高亮对应菜单栏
前端·javascript·vue.js
xiaogg367820 分钟前
系统网站首页三种常见布局vue+elementui
前端·vue.js·elementui
日升25 分钟前
AI 组件库-MateChat × 大模型:DeepSeek、OpenAI 和 阿里通义问 (Qwen)的全流程接入实战(三)
前端·ai编程·trae
Barcke25 分钟前
AI赋能开发者工具:智能提示词编写与项目管理实践
前端·后端
wordbaby31 分钟前
React Router v7 中的 `ErrorBoundary` 详解
前端·react.js