并发请求
概念:在同一时间内发出多个请求
并发请求可以针对不同的资源或者服务,其目的是为了提高文件传输效率或者请求效率。并发是计算机领域中的一个重要概念,它指的是在同一时间内处理多个任务或者请求的能力。这种能力是现代计算机系统设计的核心,它影响着系统的性能、可伸缩性和稳定性。
控制并发请求上限
1. 请求计数器
ini
/**
* requestList 任务队列
* limits 并发数量上限
* callback 回调函数
*/
function sendRequest(requestList, limits, callback) {
// 当前执行队列
const requestListWrapperedQueue = [];
// 初次执行时的并发数
const concurrentNum = Math.min(limits, requestList.length);
// 完整的执行队列
const returnPromises = [];
// 当前任务并发数
let concurrentCount = 0;
// 初次启动任务
const runTaskNeeded = () => {
let i = 0;
while (i < concurrentNum) {
i++;
runTask();
}
};
// 取出单个任务并执行
const runTask = async () => {
const task = requestListWrapperedQueue.shift();
if (task) {
try {
concurrentCount++;
const res = await task();
} catch (err) {
return err;
} finally {
concurrentCount--;
if (concurrentCount < limits &&requestListWrapperedQueue.length > 0) {
runTask();
}
}
}
};
runTaskNeeded();
}
2. Promise方式
scss
function sendRequest(requestList, limits, callback) {
// 任务队列
const promises = [];
// 当前的并发池
const pool = new Set([]);
const runTask = async () => {
// for await of 循环执行并发池
for (let requestItem of requestList) {
if (pool.size >= limits) {
await Promise.race(pool).catch((err) => err);
}
const promise = requestItem();
const cb = () => {
pool.delete(promise);
};
promise.then(cb, cb);
pool.add(promise);
promises.push(promise);
}
// 通过allSettled获取异步任务的执行结果
Promise.allSettled(promises).then(callback,callback);
};
runTask();
}
- 减少变量记录当前并发执行的请求数量
- 利用Set数据结构避免重复触发同一个请求
- 通过Promise.race将请求池数量降到限制以下
- 通过.then中的回调函数完成任务的清除
- 通过Promise.allSettled获取所有的异步结果
控制单个请求重复次数
ini
/**
* requestList 任务队列
* limits 并发数量上限
* callback 回调函数
* retryTimes 任务重试次数
*/
function sendRequest(requestList, limits, callback, retryTimes) {
const requestListWrapperedQueue = [];
const concurrentNum = Math.min(limits, requestList.length);
const returnPromises = [];
let concurrentCount = 0;
// 1. 包裹一层promise,并且将相关信息重新包装放入请求队列
const wrapePromise = (requestItem) => {
return new Promise((resolve, reject) => {
requestListWrapperedQueue.push({
requestItem,
resolve,
reject,
remainRetryTime: retryTimes,
});
});
};
const runTaskNeeded = () => {
let i = 0;
while (i < concurrentNum) {
i++;
runTask();
}
};
const runTask = async () => {
const task = requestListWrapperedQueue.shift();
if (task) {
const { requestItem, resolve, reject, remainRetryTime } = task;
try {
concurrentCount++;
const res = await requestItem();
resolve(res);
} catch (err) {
// 2. 判断是否可以再进行下次请求
if (remainRetryTime > 0) {
requestListWrapperedQueue.push(requestItem);
// 3. 更新剩余次数
task.remainRetryTime--;
} else {
reject(err);
}
} finally {
concurrentCount--;
if (concurrentCount < limits &&requestListWrapperedQueue.length > 0) {
runTask();
}
}
}
};
// 4. 构建执行队列,将每个任务都用promise包装
const init = () => {
for (let requestItem of requestList) {
const wrapperedPromise = wrapePromise(requestItem);
returnPromises.push(wrapperedPromise);
}
};
const start = () => {
init();
runTaskNeeded();
};
start();
// 5. 通过allSettled获取异步任务的执行结果
Promise.allSettled(returnPromises).then(callback, callback);
}
浏览器的并发请求
浏览器的并发请求是指浏览器能够同时发送的 HTTP 请求的数量。
为了避免对服务器造成过大的负载压力,浏览器对并发请求数量做了限制。
浏览器 | HTTP1.0 | HTTP1.1 | |
---|---|---|
IE6、7 | 2 | 4 |
IE8 | 6 | 6 |
火狐 | 6 | 6 |
Safari | 4 | 4 |
谷歌 | 6 | 6 |
Opera | 4 | 4 |
HTTP0.9、HTTP1.0每个请求都单独建立一个TCP连接,请求完成后连接断开; HTTP1.1可以持久连接,TCP建立连接后不会立即关闭,多个请求可以复用同一个TCP连接,而且多个请求可以并行发送。
浏览器对于并发请求的规则如下所示(同一域名下生效):
- 相同的 GET 请求的并发数是1,上一个请求结束,才会执行下一个请求,否则置入队列等待发送
- 不同的 GET/POST 请求的并发数量是6,当发送的请求数量达到6个,并且都没有得到响应时,后面的请求会置入队列等待发送
HTTP1.1持久链接和HTTP2.0多路复用的区别:
- HTTP1.1持久链接:多个请求共用一个TCP链接,但是同时只能发送一个请求
- HTTP2.0多路复用:在一个TCP链接中,可以同时发送多个请求
中断已发送的请求
1. 中断axios请求
javascript
import axios from 'axios';
// 创建一个 CancelToken.source 实例
const { token, cancel } = axios.CancelToken.source();
// 发送请求时,将 cancelToken 传递给 Axios 的 request 方法
axios.request({
url:'https://api.example.com/data',
method: 'get',
cancelToken: token
})
.then(response => {
... ...
})
.catch(error => {
if(axios.isCancel(error)) {
console.log("请求已取消",error.message);
}else {
console.error('请求出错',error);
}
})
cancel('请求取消原因');
当调用 cancel 函数时,会导致与 token 相关联的请求被取消。在请求被取消时,Axios 会抛出一个 Cancel 错误,我们可以通过axios.isCancel(error) 方法来判断是否是取消请求导致的错误,并进行相应的处理。
2. 中断fetch请求
ini
// 创建一个 AbortController 实例
const controller = new AbortController();
const signal = controller.signal;
// 发送请求时,将 signal 传递给 fetch 或 XMLHttpRequest
fetch('https://api.example.com/data', { signal })
.then(response => {})
.catch(error => {
if (error.name === 'AbortError'){
console.log("请求已取消");
} else {
console.error( '请求出错',error);
}
})
// 取消请求
controller.abort();
当调用 controller.abort() 方法时,会导致与 signal 相关联的请求被取消。在请求被取消时,fetch 或 XMLHttpRequest会抛出一个 AbortError 错误,我们可以在 catch 中捕获这个错误并进行相应的处理。
通过使用 AbortController 和 Abortsiqnal,我们可以在浏览器中比较方便地取消已经发送的请求,避免不必要的网络流量和资源浪费。