核心是手动控制请求发起节奏,通过“带容量限制的请求队列”实现并发数限制:维护运行中请求数与待发队列,空闲时自动调度;或用 async/await + 滑动窗口,基于 promise.race 动态补位,保持恒定并发量。

用 fetch 实现并发请求数量限制,核心是**手动控制请求的发起节奏**,避免同时发出过多请求压垮服务端或触发浏览器限制(通常为 6 个同源并发)。不能靠 Promise.all 直接发一堆,而要构造一个“带容量限制的请求队列”。
用 Promise 队列 + 计数器控制并发数
最轻量、不依赖第三方库的方式:维护一个运行中请求数(running)和待发队列(queue),每次有空位就从队列取一个执行,并在请求结束后自动调度下一个。
示例代码:
class FetchQueue {
constructor(limit = 3) {
this.limit = limit;
this.running = 0;
this.queue = [];
}
async add(url, options = {}) {
return new Promise((resolve, reject) => {
this.queue.push({ url, options, resolve, reject });
this.#process();
});
}
async #process() {
if (this.running >= this.limit || this.queue.length === 0) return;
const { url, options, resolve, reject } = this.queue.shift();
this.running++;
fetch(url, options)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json(); // 或 res.text() 等,按需调整
})
.then(data => resolve(data))
.catch(err => reject(err))
.finally(() => {
this.running--;
this.#process(); // 尝试调度下一个
});
}
}
// 使用示例
const queue = new FetchQueue(2); // 最多 2 个并发
const urls = ['/api/user/1', '/api/user/2', '/api/user/3', '/api/user/4'];
const promises = urls.map(url => queue.add(url));
Promise.all(promises)
.then(results => console.log('全部完成:', results))
.catch(err => console.error('出错:', err));
用 async/await + 滑动窗口更清晰(推荐)
把请求数组分块处理:每次最多启动 limit 个请求,等其中任一完成,立刻补上新请求,保持“始终最多 limit 个在跑”。逻辑更直观,也更容易加超时、重试等扩展。
关键点:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 用
Promise.race监听最快完成的请求 - 用
Map记录每个 pending 请求的索引和 resolve/reject - 完成一个后,从剩余队列里取下一个补位
简化版实现:
async function fetchWithLimit(urls, options = {}, limit = 3) {
const results = new Array(urls.length);
const pending = new Map(); // key: index, value: { resolve, reject }
let nextIndex = 0;
// 启动初始批次
for (let i = 0; i p.promise));
return results;
function startRequest(index) {
if (index >= urls.length) return;
const promise = fetch(urls[index], options)
.then(res => {
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
})
.then(data => {
results[index] = { data, error: null };
})
.catch(err => {
results[index] = { data: null, error: err };
});
pending.set(index, { promise });
nextIndex = index + 1;
// 完成后立即补新请求
promise.finally(() => {
pending.delete(index);
if (nextIndex
用 p-limit 库快速落地(生产推荐)
如果你不想重复造轮子,p-limit 是最常用、稳定、小巧(仅 2KB)的并发控制工具。它返回一个函数,调用即入队,自动限流。
安装与使用:
import pLimit from 'p-limit'; const limit = pLimit(3); const urls = ['/api/a', '/api/b', '/api/c']; const promises = urls.map(url => limit(() => fetch(url).then(r => r.json())) ); Promise.all(promises).then(console.log);
优势:支持取消、错误传播、类型推导(TS 友好),且已通过大量线上验证。
注意事项和进阶点
实际使用时注意这些细节:
-
错误不要吞掉:确保
catch后仍调用reject或标记结果,否则队列可能卡住 -
AbortSignal 可集成:可在每个
fetch中传入统一的AbortController.signal实现整批取消 -
节流 ≠ 重试:并发限制不解决失败问题,如需重试,应在单个
fetch内部封装(如用retry-axios或手写 retry 逻辑) -
响应体处理要一致:避免有的用
.json()、有的用.text()导致类型混乱,建议统一封装响应处理器
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










