编辑
2025-07-07
前端
00

目录

直接上代码:一个即插即用的请求队列
如何使用?三步搞定!
它是如何工作的?

有这样一些场景:

  • 页面一加载,需要同时发 10 个请求,结果页面卡住,服务器也快崩了。
  • 用户可以批量操作,一次点击触发了几十个上传文件的请求,浏览器直接转圈圈。

当后端处理不过来时,前端一股脑地把请求全发过去,只会让情况更糟。

核心思想就一句话:不要一次性把所有请求都发出去,让它们排队,一个一个来,或者一小批一小批来。

这就好比超市结账,只有一个收银台,却来了100个顾客。最好的办法就是让他们排队,而不是一拥而上。我们的“请求队列”就是这个“排队管理员”。

直接上代码:一个即插即用的请求队列

不用复杂的分析,直接复制下面的 RequestPool 类到我们的项目里。它非常小巧,只有不到 40 行代码。

js
/**  * 一个简单的请求池/请求队列,用于控制并发  * @example  * const pool = new RequestPool(3); // 限制并发数为 3  * pool.add(() => myFetch('/api/1'));  * pool.add(() => myFetch('/api/2'));  */ class RequestPool {  /**    * @param {numberlimit - 并发限制数    */  constructor(limit = 3) {     this.limit = limit; // 并发限制数     this.queue = [];    // 等待的请求队列     this.running = 0;   // 当前正在运行的请求数   }  /**    * 添加一个请求到池中    * @param {FunctionrequestFn - 一个返回 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();   // 尝试运行下一个         });     }   } }

如何使用?三步搞定!

假设你有一个请求函数 mockApi,它会模拟一个比较慢的接口。

image.png

发生了什么?

当你运行上面的代码,你会看到:

  1. [1] 和 [2] 的请求几乎同时开始。
  2. [3]、[4]、[5]、[6] 在乖乖排队。
  3. 当 [1] 或 [2] 中任意一个完成后,队列中的 [3] 马上就会开始。
  4. 整个过程,同时运行的请求数永远不会超过 2 个。

控制台输出类似这样:

[1] 🚀 请求开始...
[2] 🚀 请求开始...
// (此时 3, 4, 5, 6 在排队)

[1] ✅ 请求完成!
[1] 收到结果: 任务 1 的结果
[3] 🚀 请求开始...   // 1号完成,3号立刻补上

[2] ✅ 请求完成!
[2] 收到结果: 任务 2 的结果
[4] 🚀 请求开始...   // 2号完成,4号立刻补上

...

它是如何工作的?

  1. add(requestFn): 你扔给它的不是一个已经开始的请求,而是一个“启动器”函数 () => mockApi(i)。它把这个“启动器”放进 queue 数组里排队。
  2. _run(): 这是管理员。它会检查:
  • 现在有空位吗?(running < limit)
  • 有人在排队吗?(queue.length > 0)
  • 如果两个条件都满足,就从队首叫一个号(queue.shift()),让它开始工作(执行 requestFn()),并且把正在工作的计数 running 加一。
  1. finally(): 这是最关键的一步。每个请求不管是成功还是失败,最后都会执行 finally 里的代码。它会告诉管理员:“我完事了!”,然后把 running 减一,并再次呼叫管理员 _run() 来看看能不能让下一个人进来。

这样就形成了一个完美的自动化流程:完成一个,就自动启动下一个

以后再遇到需要批量发请求的场景,别再用 Promise.all 一股脑全发出去了。

把上面那段小小的 RequestPool 代码复制到你的项目里,用它来包裹我们的请求函数。只需要设置一个合理的并发数(比如 2 或 3),就能在不修改后端代码的情况下,大大减轻服务器的压力,让我们的应用运行得更平稳。

这是一种简单、优雅且非常有效的前端优化手段。

本文作者:小白菜

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!