前端必备!用请求队列轻松控制批量请求,告别浏览器卡死与服务器崩溃
在前端开发中,我们经常遇到这样的场景:
- 页面一加载,就同时发出 10 个甚至更多请求,导致页面卡顿,服务器压力骤增。
- 用户批量上传几十个文件,浏览器直接转圈圈,甚至整个应用冻结。
问题的根本原因很简单:前端一股脑地把所有请求发出去,浏览器和服务器都无法承受。
解决思路也很直接:不要一次性发所有请求,让请求排队,一个一个或小批量发送。这就像超市结账,只有一个收银台,顾客排队结账,而不是一拥而上。
本文将教你如何用一个小巧的“请求队列”来优雅地控制批量请求,让应用更加平稳。
一、请求队列原理
请求队列的核心是:
- 排队:把请求放进队列中等待执行。
- 限制并发数:一次最多允许 n 个请求同时执行。
- 自动补位:每当一个请求完成(成功或失败),队列中的下一个请求自动启动。
这样可以大幅降低浏览器卡顿和服务器压力。
二、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 个。
四、为什么这样有效
- 防止浏览器卡死:同时发送过多请求会占用过多浏览器资源,队列机制避免了这种情况。
- 减轻服务器压力:后端只处理可控数量的请求,不会瞬间被压垮。
- 简洁优雅:
RequestPool
自动管理队列和并发,无需复杂逻辑。 - 可扩展:可随时修改并发数,适应不同场景需求。
五、实用建议
- 并发数可以根据业务场景调整:上传文件场景可以设置 2
3,数据请求场景可以 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>