编程 前端必备!用请求队列轻松控制批量请求,告别浏览器卡死与服务器崩溃

2025-08-16 09:14:37 +0800 CST views 38

前端必备!用请求队列轻松控制批量请求,告别浏览器卡死与服务器崩溃

在前端开发中,我们经常遇到这样的场景:

  • 页面一加载,就同时发出 10 个甚至更多请求,导致页面卡顿,服务器压力骤增。
  • 用户批量上传几十个文件,浏览器直接转圈圈,甚至整个应用冻结。

问题的根本原因很简单:前端一股脑地把所有请求发出去,浏览器和服务器都无法承受

解决思路也很直接:不要一次性发所有请求,让请求排队,一个一个或小批量发送。这就像超市结账,只有一个收银台,顾客排队结账,而不是一拥而上。

本文将教你如何用一个小巧的“请求队列”来优雅地控制批量请求,让应用更加平稳。


一、请求队列原理

请求队列的核心是:

  1. 排队:把请求放进队列中等待执行。
  2. 限制并发数:一次最多允许 n 个请求同时执行。
  3. 自动补位:每当一个请求完成(成功或失败),队列中的下一个请求自动启动。

这样可以大幅降低浏览器卡顿和服务器压力。


二、RequestPool 实现

下面这个 RequestPool 类只有不到 40 行代码,直接复制到项目即可使用:

/**
 * 一个简单的请求池/请求队列,用于控制并发
 * @example
 * const pool = new RequestPool(3); // 限制并发数为 3
 * pool.add(() => myFetch('/api/1'));
 * pool.add(() => myFetch('/api/2'));
 */
class RequestPool {
  /**
   * @param {number} limit - 并发限制数
   */
  constructor(limit = 3) {
    this.limit = limit;   // 并发限制数
    this.queue = [];      // 等待的请求队列
    this.running = 0;     // 当前正在运行的请求数
  }

  /**
   * 添加一个请求到池中
   * @param {Function} requestFn - 一个返回 Promise 的函数
   * @returns {Promise}
   */
  add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this._run(); // 每次添加后,尝试运行
    });
  }

  _run() {
    while (this.running < this.limit && this.queue.length > 0) {
      const { requestFn, resolve, reject } = this.queue.shift();
      this.running++;

      requestFn()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          this.running--;  // 请求完成,空出位置
          this._run();     // 尝试运行下一个
        });
    }
  }
}

三、如何使用 RequestPool

假设你有一个模拟接口 mockApi,返回一个 Promise 并带有延迟:

const mockApi = (id) => {
  return new Promise((resolve) => {
    console.log(`[${id}] 🚀 请求开始...`);
    setTimeout(() => {
      console.log(`[${id}] ✅ 请求完成!`);
      resolve(`任务 ${id} 的结果`);
    }, 1000 + Math.random() * 2000);
  });
};

然后用 RequestPool 控制并发:

const pool = new RequestPool(2); // 同时最多允许 2 个请求

for (let i = 1; i <= 6; i++) {
  pool.add(() => mockApi(i)).then(result => {
    console.log(`[${i}] 收到结果:`, result);
  });
}

运行效果

  • [1][2] 请求几乎同时开始,[3][4][5][6] 在队列中等待。
  • [1][2] 完成后,队列中的下一个请求(如 [3])立即启动。
  • 整个过程中,同时运行的请求数永远不会超过限制的 2 个。

四、为什么这样有效

  1. 防止浏览器卡死:同时发送过多请求会占用过多浏览器资源,队列机制避免了这种情况。
  2. 减轻服务器压力:后端只处理可控数量的请求,不会瞬间被压垮。
  3. 简洁优雅RequestPool 自动管理队列和并发,无需复杂逻辑。
  4. 可扩展:可随时修改并发数,适应不同场景需求。

五、实用建议

  • 并发数可以根据业务场景调整:上传文件场景可以设置 23,数据请求场景可以 35。
  • 批量请求函数不要直接执行,而是传递函数(() => fetch(...)),让队列来调用。
  • 可以结合 Promise.all 来收集结果,但不要直接一次性发出所有请求。

六、总结

RequestPool 是一种简单却非常有效的前端优化手段:

  • 避免大量请求同时涌入造成浏览器卡顿和服务器压力。
  • 让批量操作、文件上传等场景更平滑。
  • 只需几十行代码,即插即用,适合任何项目。

下一次再遇到“用户一股脑点按钮发几十个请求”的场景,不必惊慌,把请求放进队列,让它们排队执行,前端和后端都会感谢你。

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>RequestPool Demo</title>
<style>
  body { font-family: Arial, sans-serif; padding: 20px; }
  button { padding: 10px 20px; margin-bottom: 20px; cursor: pointer; }
  ul { list-style: none; padding: 0; }
  li { margin-bottom: 5px; }
  .running { color: green; font-weight: bold; }
  .queued { color: orange; }
  .done { color: gray; text-decoration: line-through; }
</style>
</head>
<body>
<h2>RequestPool Demo:批量请求排队控制</h2>
<button id="startBtn">开始发送 10 个请求</button>
<ul id="log"></ul>

<script>
/**
 * RequestPool 实现
 */
class RequestPool {
  constructor(limit = 3) {
    this.limit = limit;
    this.queue = [];
    this.running = 0;
  }

  add(requestFn) {
    return new Promise((resolve, reject) => {
      this.queue.push({ requestFn, resolve, reject });
      this._run();
    });
  }

  _run() {
    while (this.running < this.limit && this.queue.length > 0) {
      const { requestFn, resolve, reject } = this.queue.shift();
      this.running++;
      updateUI(requestFn.id, 'running');

      requestFn()
        .then(resolve)
        .catch(reject)
        .finally(() => {
          this.running--;
          updateUI(requestFn.id, 'done');
          this._run();
        });
    }
  }
}

/**
 * 模拟接口函数
 */
const mockApi = (id) => {
  const fn = () => new Promise((resolve) => {
    setTimeout(() => resolve(`任务 ${id} 完成`), 1000 + Math.random() * 2000);
  });
  fn.id = id; // 保存 id 用于 UI 显示
  return fn;
};

/**
 * UI 更新函数
 */
const logEl = document.getElementById('log');
const updateUI = (id, status) => {
  let li = document.getElementById(`task-${id}`);
  if (!li) {
    li = document.createElement('li');
    li.id = `task-${id}`;
    li.textContent = `任务 ${id}`;
    logEl.appendChild(li);
  }
  li.className = status;
};

/**
 * 开始按钮逻辑
 */
document.getElementById('startBtn').addEventListener('click', () => {
  const pool = new RequestPool(3); // 并发数限制为 3
  for (let i = 1; i <= 10; i++) {
    const requestFn = mockApi(i);
    updateUI(i, 'queued');
    pool.add(requestFn).then(result => console.log(result));
  }
});
</script>
</body>
</html>

复制全文 生成海报 前端开发 性能优化 JavaScript

推荐文章

html一个全屏背景视频
2024-11-18 00:48:20 +0800 CST
实现微信回调多域名的方法
2024-11-18 09:45:18 +0800 CST
Mysql允许外网访问详细流程
2024-11-17 05:03:26 +0800 CST
gin整合go-assets进行打包模版文件
2024-11-18 09:48:51 +0800 CST
如何将TypeScript与Vue3结合使用
2024-11-19 01:47:20 +0800 CST
网络数据抓取神器 Pipet
2024-11-19 05:43:20 +0800 CST
Java环境中使用Elasticsearch
2024-11-18 22:46:32 +0800 CST
一个数字时钟的HTML
2024-11-19 07:46:53 +0800 CST
Vue3中的虚拟滚动有哪些改进?
2024-11-18 23:58:18 +0800 CST
PHP 8.4 中的新数组函数
2024-11-19 08:33:52 +0800 CST
windon安装beego框架记录
2024-11-19 09:55:33 +0800 CST
PHP 如何输出带微秒的时间
2024-11-18 01:58:41 +0800 CST
Rust 高性能 XML 读写库
2024-11-19 07:50:32 +0800 CST
MySQL 优化利剑 EXPLAIN
2024-11-19 00:43:21 +0800 CST
为什么大厂也无法避免写出Bug?
2024-11-19 10:03:23 +0800 CST
最全面的 `history` 命令指南
2024-11-18 21:32:45 +0800 CST
ElasticSearch 结构
2024-11-18 10:05:24 +0800 CST
使用 Git 制作升级包
2024-11-19 02:19:48 +0800 CST
程序员茄子在线接单