前端请求并发控制技术:优化性能提升用户体验

903 阅读3分钟

在前端开发中,我们经常需要处理大量的异步请求,例如从后端获取数据或资源。然而,同时发起过多的并发请求可能会导致性能问题,导致页面加载缓慢或甚至崩溃。为了优化性能并提升用户体验,我们可以使用请求并发控制技术来限制同时进行的请求数量,避免过度消耗资源。

问题背景

在前端开发中,当需要向后端服务器发送多个异步请求时,通常会使用 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('上传失败');
  }
}