关于浏览器的请求并发
浏览器本身会有请求队列,控制请求并发的数量,一般都为 6-8 个,每款浏览器相差不大。当发送大批量请求时,浏览器一次只会向服务器发送 6-8 个请求,其他 pending 状态的请求都在本地排队,并没有发送到服务端,所以浏览器的并发队列已经解决单个客户端的大并发问题了。浏览器一次性发出大批量请求时,主要影响的是本地客户端(可能硬件配置低),对服务端影响很小。
看图:绿色线条代表正在发送的请求时间,灰色线条代表正在等待的请求时间,蓝色的线条代表正在接受响应时间。
此处我同时发送了 12 个请求,而浏览器最初仅向服务器发送了 6 个请求(绿色箭头),而剩下的 6 个进入等待状态(灰色箭头),而当有请求完成时,浏览器又向服务器发送了新的请求,保持了多个请求的并发(红框)。
请求池
场景:当需要发送大批量请求,例如大文件切片上传时可能会发送上千次请求,会造成网络拥塞,也可能会导致浏览器直接奔溃。
解决思路:通过 promise、async/await,创建一个请求池,将当前的请求限制在固定的数量,每当其中一个请求完成后立马发送新的,保证请求池中的请求一直是满的。
实现:
// 模拟一个异步请求函数
function mockRequest(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(`Request ${id} completed`);
resolve(id);
}, Math.random() * 2000); // 随机延迟时间来模拟请求处理
});
}
// 请求池执行函数
async function requestPool(requests, poolSize) {
const results = []; // 存储结果
const executing = new Set(); // 正在执行的请求集合
for (const request of requests) {
// 如果正在执行的请求达到了池子的大小,就等待一个请求完成
if (executing.size >= poolSize) {
await Promise.race(executing);
}
/**
开始一个新请求
这里这么写的原因是,创建一个立即执行的 Promise 然后返回 request(),这样无论 request
是否是一个异步函数,返回的 p 都会是一个 Promise 对象, p 就会存在 .then 方法,增加代码的
健壮性。
*/
const p = Promise.resolve().then(() => request());
results.push(p); // 将请求的结果添加到结果数组
// 请求开始执行时添加到正在执行的集合
executing.add(p);
// 请求完成后从正在执行的集合移除
p.then(() => executing.delete(p));
}
// 等待所有请求完成
return Promise.all(results);
}
// 创建100个请求
const allRequests = Array.from({ length: 100 }, (_, index) => () => mockRequest(index));
// 使用请求池执行所有请求,池大小为5
requestPool(allRequests, 5)
.then(() => console.log('All requests completed'))
.catch(error => console.error('An error occurred:', error));
请求池的存在的意义:
- 浏览器本身可以限制正在发送的并发数量,但是如果队列中等待的请求过多浏览器也可能会崩溃,而请求池可以控制当前的请求队列中的数量,使请求不需要等待,当有一个请求结束后立即放入新请求,保证请求队列中只有正在发送的请求,减少等待中的请求。
- 浏览器限制并发数为 6-8 个,当除了发送大批量的请求外,我还需要同时发送其他请求,这时就可以通过请求池的并发限制,减少池中正在发送的数量,使浏览器请求队列中空出位置,保证我所需的请求可以正常发送,不会因为排队而延迟,可以更精确的控制并发数量。