在前端开发中,我们经常需要处理大量的异步请求,例如从后端获取数据或资源。然而,同时发起过多的并发请求可能会导致性能问题,导致页面加载缓慢或甚至崩溃。为了优化性能并提升用户体验,我们可以使用请求并发控制技术来限制同时进行的请求数量,避免过度消耗资源。
问题背景
在前端开发中,当需要向后端服务器发送多个异步请求时,通常会使用 fetch 或 axios等工具来发起请求。如果同时发起大量的异步请求,服务器可能无法承受如此大的负载,导致性能下降。此外,浏览器也有并发请求的限制,过多的同时进行的请求可能会被浏览器阻塞,影响页面加载速度。
解决方案:请求并发控制
为了解决并发请求带来的性能问题,我们可以采用请求并发控制技术。通过限制同时进行的请求数量,我们可以有效控制请求的并发性,避免过载服务器和浏览器。
实现思路
一种常见的实现思路是使用异步函数、Promise 和 async/await 来控制并发请求数。下面是一个简单的示例代码:
async function sendRequests(urls, maxConcurrent) {
const inFlight = [];
for (let url of urls) {
const request = fetch(url);
inFlight.push(request);
if (inFlight.length >= maxConcurrent) {
await Promise.race(inFlight);
}
}
await Promise.all(inFlight);
}
// 调用 sendRequests 函数
sendRequests(['url1', 'url2', 'url3', 'url4', 'url5'], 3);
1. 定义了一个名为 `sendRequests` 的异步函数,接受一个包含要请求的 URL 的数组 `urls` 作为参数,并设置了最大并发请求数 `maxConcurrent` 为 6,以及一个空数组 `inFlight` 用于存储当前正在进行的请求。
2. 遍历 `urls` 数组,对每个 URL 发起一个 fetch 请求,并将返回的 Promise 对象添加到 `inFlight` 数组中。
3. 在每次添加请求到 `inFlight` 数组后,检查当前正在进行的请求数量是否已经达到最大并发数 `maxConcurrent`。如果达到最大并发数,使用 `Promise.race()` 方法等待其中任何一个请求完成(即任何一个 Promise 对象的状态变为 resolved 或 rejected)。
4. 一旦有请求完成(无论成功或失败),继续向 `inFlight` 数组中添加新的请求,保持并发请求数不超过最大并发数。
5. 当所有请求都添加到 `inFlight` 数组后,使用 `Promise.all()` 方法等待所有请求都完成。这样可以确保所有请求都已经处理完毕。
场景
有一个发票管理模块,实现了用户上传单张发票,系统进行ocr识别,验真,验重,保存。现在需要实现批量操作:同时上传多张发票图片,进行自动进行上述操作。如果前端循环去一次调用上述流程的接口,一次性上传几十张的话,会造成服务器压力很大,为了解决这个问题就可以使用上述方式。
使用
在uniapp使用,vue类似
const uploadTax = () => {
uni.chooseImage({
count: 9,
success(res) {
const tempFilePaths: string[] = res.tempFilePaths as []
console.log('tempFilePaths', tempFilePaths)
// 控制并发请求数量为3
sendRequests(tempFilePaths, 3);
}
})
}
let successCount = 0;
let failCount = 0;
let uploadErrmsg = [];
let uploadErrmsgStr = '';
const sendRequests = async (tempFilePaths: string[], maxConcurrent: number) => {
const inFlight = [];
for (let i = 0; i < tempFilePaths.length; i++) {
const filePath = tempFilePaths[i];
const request = uploadImgs(tempFilePaths,filePath);
inFlight.push(request);
if (inFlight.length >= maxConcurrent) {
await Promise.race(inFlight);
}
}
await Promise.all(inFlight);
}
const uploadImgs = async (tempFilePaths:string[],filePath: string) => {
const action = import.meta.env.VITE_APP_TAX_API_URL;
uploadBtnLoading.value = true;
try {
const res = await uni.uploadFile({
url: `${action}/tax-invoice/upload-tax-invoice`,
filePath,
name: 'multipartFile',
header: {
token: uni.getStorageSync('ut'),
platForm: 'pc',
'Content-Type': 'multipart/form-data'
}
});
const data = JSON.parse(res.data);
console.log('上传结果', data);
if (data.status != 200) {
uploadErrmsg.push(data.message);
uploadErrmsgStr = uploadErrmsg.join(';');
failCount++;
console.log('上传失败', data.message);
} else {
console.log('上传成功', data.message);
successCount++;
}
uploadCount.value++;
if (uploadCount.value === tempFilePaths.length) {
alertDialog.value.open();
let meassageInfo =
failCount == 0
? `已上传${tempFilePaths.length}张,成功${successCount}张`
: `已上传${tempFilePaths.length}张,成功${successCount}张,失败${failCount}张;\n失败原因:${uploadErrmsgStr}`;
uploadError.value = meassageInfo;
successCount = 0;
failCount = 0;
uploadErrmsg = [];
uploadErrmsgStr = '';
uploadBtnLoading.value = false;
uploadCount.value = 0;
resetParam();
_processApplySearch();
_getStatusNums();
}
} catch (error) {
uploadBtnLoading.value = false;
toast('上传失败');
}
}