在前端开发中,一次性发起大量网络请求可能会导致以下问题:
-
浏览器的并发连接数限制
-
服务器压力过大、服务器过载
-
用户设备资源消耗过高
-
网络拥塞
首先用 nodejs 搭建一个模拟服务器
const express = require("express");
const cors = require("cors");
const app = express();
const PORT = 3000;
// 启用CORS以允许前端访问
app.use(cors());
// 记录并发请求的状态
let activeRequests = 0;
let maxConcurrent = 0;
let totalRequests = 0;
let requestLog = [];
// 模拟延迟的API端点
app.get("/api/test/:id", (req, res) => {
const id = req.params.id;
const delay = parseInt(req.query.delay) || 2000; // 默认延迟2秒
// 记录请求开始
const startTime = Date.now();
activeRequests++;
totalRequests++;
// 更新最大并发数
if (activeRequests > maxConcurrent) {
maxConcurrent = activeRequests;
}
console.log(`请求 #${id} 开始处理 (当前并发: ${activeRequests})`);
// 模拟API延迟
setTimeout(() => {
// 请求完成
activeRequests--;
const endTime = Date.now();
// 记录到日志
requestLog.push({
id,
startTime,
endTime,
duration: endTime - startTime,
});
console.log(`请求 #${id} 完成 (剩余并发: ${activeRequests})`);
// 返回结果
res.json({
requestId: id,
message: `请求 #${id} 成功完成`,
duration: endTime - startTime,
});
}, delay);
});
// 获取服务器统计信息
app.get("/api/stats", (req, res) => {
res.json({
activeRequests,
maxConcurrent,
totalRequests,
requestLog,
});
});
// 重置统计信息
app.post("/api/reset", (req, res) => {
activeRequests = 0;
maxConcurrent = 0;
totalRequests = 0;
requestLog = [];
res.json({ message: "统计数据已重置" });
});
app.listen(PORT, () => {
console.log(`测试服务器运行在 http://localhost:${PORT}`);
});
以下是几种常见的控制并发请求的方法
1、请求队列+并发控制
// 请求控制器类
class RequestController {
constructor(maxConcurrent = 5) {
this.maxConcurrent = maxConcurrent; // 最大并发数
this.queue = []; // 请求队列
this.runningCount = 0; // 当前运行的请求数
}
async add(promiseFunc) {
if (this.runningCount >= this.maxConcurrent) {
// 等待执行
await new Promise((resolve) => this.queue.push(resolve));
}
return this._run(promiseFunc);
}
async _run(promiseFunc) {
this.runningCount++;
try {
return await promiseFunc();
} finally {
this.runningCount--;
// 执行队列中的下一个请求
if (this.queue.length > 0) {
const next = this.queue.shift();
next();
}
}
}
}
// 使用示例
const controller = new RequestController(3); // 并发控制为3
// 模拟大量请求
const requests = Array(40)
.fill(0)
.map((_, index) => {
return () => {
return fetch(`http://localhost:3000/api/test/${index}`).then((res) =>
res.json()
);
};
});
// 发起所有请求,但控制并发数
Promise.all(requests.map((req) => controller.add(req))).then((results) =>
console.log("所有请求完成")
);
2、分批发送请求
async function sendRequestsInBatches(requests, batchSize = 5) {
const results = [];
for (let i = 0; i < requests.length; i += batchSize) {
const batch = requests.slice(i, i + batchSize);
const batchResults = await Promise.all(batch.map((req) => req()));
results.push(...batchResults);
}
return results;
}
// 使用示例
// 模拟大量请求
const requests = Array(40)
.fill(0)
.map((_, index) => {
return () => {
return fetch(`http://localhost:3000/api/test/${index}`).then((res) =>
res.json()
);
};
});
(async function () {
const results = await sendRequestsInBatches(requests, 8);
})();
3、使用信号量控制
class Semaphore {
constructor(max) {
this.max = max;
this.count = 0;
this.queue = [];
}
async acquire() {
if (this.count < this.max) {
this.count++;
return Promise.resolve();
}
return new Promise((resolve) => this.queue.push(resolve));
}
release() {
this.count--;
if (this.queue.length > 0) {
this.count++;
const resolve = this.queue.shift();
resolve();
}
}
}
// 模拟大量请求
const requests = Array(40)
.fill(0)
.map((_, index) => {
return () => {
return fetch(`http://localhost:3000/api/test/${index}`).then((res) =>
res.json()
);
};
});
// 使用信号量控制 示例
async function useSemaphore(requests, limit) {
const semaphore = new Semaphore(limit);
return Promise.all(
requests.map(async (req) => {
await semaphore.acquire();
try {
return await req();
} finally {
semaphore.release();
}
})
);
}
(async function () {
const results = await useSemaphore(requests, 5);
})();
5、async/await 控制
async function sendWithConcurrencyLimit(urls, limit = 5) {
const results = [];
const executing = new Set();
for (const url of urls) {
const p = fetch(url)
.then((r) => r.json())
.then((data) => {
executing.delete(p);
return data;
});
executing.add(p);
results.push(p);
if (executing.size >= limit) {
// 等待任意一个请求完成
await Promise.race(executing);
}
}
// 等待所有请求完成
return Promise.all(results);
}
// 模拟大量请求
const requests = Array(40)
.fill(0)
.map((_, index) => {
return () => {
return fetch(`http://localhost:3000/api/test/${index}`).then((res) =>
res.json()
);
};
});
(async function () {
const results = await sendWithConcurrencyLimit(requests, 5);
})();
6、使用第三方库
// 使用 p-limit
import pLimit from "p-limit";
const limit = pLimit(5); // 最大并发数为5
const requests = Array(50)
.fill(0)
.map((_, i) => {
return () =>
fetch(`http://localhost:3000/api/test/${index}`).then((r) => r.json());
});
Promise.all(requests.map((req) => limit(() => req()))).then((results) =>
console.log("所有请求完成")
);