有这样一些场景:
- 页面一加载,需要同时发 10 个请求,结果页面卡住,服务器也快崩了。
- 用户可以批量操作,一次点击触发了几十个上传文件的请求,浏览器直接转圈圈。
当后端处理不过来时,前端一股脑地把请求全发过去,只会让情况更糟。
** 核心思想就一句话:不要一次性把所有请求都发出去,让它们排队,一个一个来,或者一小批一小批来。 **
这就好比超市结账,只有一个收银台,却来了100个顾客。最好的办法就是让他们排队,而不是一拥而上。我们的“请求队列”就是这个“排队管理员”。
直接上代码:一个即插即用的请求队列
不用复杂的分析,直接复制下面的 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(); // 尝试运行下一个
});
}
}
}
如何使用?三步搞定!
假设你有一个请求函数 mockApi ,它会模拟一个比较慢的接口。

** 发生了什么? **
当你运行上面的代码,你会看到:
[1]和[2]的请求几乎同时开始。[3]、[4]、[5]、[6]在乖乖排队。- 当
[1]或[2]中任意一个完成后,队列中的[3]马上就会开始。 - 整个过程,同时运行的请求数 ** 永远不会超过 2 个 ** 。
** 控制台输出类似这样: **
[1] 🚀 请求开始...
[2] 🚀 请求开始...
// (此时 3, 4, 5, 6 在排队)
[1] ✅ 请求完成!
[1] 收到结果: 任务 1 的结果
[3] 🚀 请求开始... // 1号完成,3号立刻补上
[2] ✅ 请求完成!
[2] 收到结果: 任务 2 的结果
[4] 🚀 请求开始... // 2号完成,4号立刻补上
...
它是如何工作的?
- **
add(requestFn)** : 你扔给它的不是一个已经开始的请求,而是一个“启动器”函数() => mockApi(i)。它把这个“启动器”放进queue数组里排队。 - **
_run()** : 这是管理员。它会检查:- 现在有空位吗?(
running < limit) - 有人在排队吗?(
queue.length > 0) - 如果两个条件都满足,就从队首叫一个号(
queue.shift()),让它开始工作(执行requestFn()),并且把正在工作的计数running加一。
- 现在有空位吗?(
- **
.finally()** : 这是最关键的一步。每个请求不管是成功还是失败,最后都会执行finally里的代码。它会告诉管理员:“我完事了!”,然后把running减一,并再次呼叫管理员_run()来看看能不能让下一个人进来。
这样就形成了一个完美的自动化流程: ** 完成一个,就自动启动下一个 ** 。
以后再遇到需要批量发请求的场景,别再用 Promise.all 一股脑全发出去了。
把上面那段小小的 RequestPool 代码复制到你的项目里,用它来包裹我们的请求函数。只需要设置一个合理的并发数(比如 2 或
3),就能在不修改后端代码的情况下,大大减轻服务器的压力,让我们的应用运行得更平稳。
这是一种简单、优雅且非常有效的前端优化手段。