前端并发请求控制
TIP
在前端处理大量请求时,控制并发是优化性能、避免资源竞争或服务器压力的关键手段。以下是几种常见的并发控制方案,从简单到复杂逐步递进。
1. 原生 Promise.all
的缺陷
默认情况下,Promise.all
会一次性发起所有请求,可能导致浏览器或服务器过载:
javascript
// 一次性并发所有请求(不推荐)
const requests = [fetch(url1), fetch(url2), ..., fetch(url50)];
Promise.all(requests).then(results => { ... });
2. 手动实现并发队列
通过队列机制控制同时进行的请求数量,例如每次最多同时发送 N 个请求。
实现代码示例
javascript
function controlledConcurrency(urls, maxConcurrent = 5) {
let index = 0;
const results = [];
// 递归执行单个请求
const runNext = async () => {
if (index >= urls.length) return;
const currentIndex = index++;
try {
const response = await fetch(urls[currentIndex]);
results[currentIndex] = response;
} catch (error) {
results[currentIndex] = error;
}
await runNext(); // 递归启动下一个请求
};
// 初始化并发池
const workers = Array(Math.min(maxConcurrent, urls.length))
.fill(null)
.map(runNext);
return Promise.all(workers).then(() => results);
}
// 使用示例
const urls = [url1, url2, ..., url50];
controlledConcurrency(urls, 5).then(results => {
console.log('所有请求完成:', results);
});
3. 使用第三方库(推荐)
利用现成的工具库简化代码,例如:
p-limit
(轻量级并发控制)async-pool
(基于 Promise 的并发池)
使用 p-limit
示例
javascript
import pLimit from 'p-limit';
const limit = pLimit(5); // 最大并发数 5
const urls = [url1, url2, ..., url50];
const tasks = urls.map(url =>
limit(() => fetch(url).then(res => res.json()))
);
Promise.all(tasks).then(results => {
console.log('所有请求完成:', results);
});
4. 分批次处理
将请求分成多个批次(Chunk),逐批次执行,适合对实时性要求不高的场景:
javascript
async function batchRequests(urls, batchSize = 5) {
const results = [];
for (let i = 0; i < urls.length; i += batchSize) {
const batch = urls.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map(url => fetch(url)));
results.push(...batchResults);
}
return results;
}
// 使用示例
batchRequests(urls, 5).then(results => { ... });
5. 动态调整并发数
根据网络状态或服务器负载动态调整并发数,需结合具体业务逻辑(如错误重试、优先级等):
javascript
class DynamicConcurrency {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent;
this.queue = [];
this.activeCount = 0;
}
add(task) {
this.queue.push(task);
this.next();
}
next() {
while (this.queue.length > 0 && this.activeCount < this.maxConcurrent) {
const task = this.queue.shift();
this.activeCount++;
task().finally(() => {
this.activeCount--;
this.next();
});
}
}
}
// 使用示例
const controller = new DynamicConcurrency(5);
urls.forEach(url => {
controller.add(() => fetch(url).then(res => res.json()));
});
关键优化点
避免浏览器资源耗尽:浏览器对同一域名有并发限制(通常 6-8 个),控制并发可减少排队时间。
错误处理:在并发控制中加入重试机制(如指数退避)和错误捕获。
优先级控制:为高优先级请求插队(如用户触发的操作)。
取消请求:结合
AbortController
允许取消未完成的请求。
总结
- 简单场景:使用
p-limit
或手动队列。 - 复杂场景:动态调整并发数、分批次处理或结合优先级。
- 终极方案:结合并发控制、错误重试、取消机制和性能监控。
通过合理控制并发,可以显著提升前端应用的稳定性和用户体验!