大文件上传使用分包并且返回进度条进度

347 阅读2分钟

问题描述:

在上传大文件的时候,有时候太大或者网络不好,用户可能会等待太久而不知道上传了多少。

解决思路:

使用异步返回一个文件上传返回的进度数,再添加UI显示到页面上。

优化:

大文件上传使用异步分包处理,加快文件上传的速度。

中途问题:

刚开始使用原生js,采用XMLHttpRequest、fetch方法。

XMLHttpRequest方法一直堵塞,需要一个包上传完成之后才可以上传第二个包。 fetch方法使用保存fetch的返回结果的Promise到一个Promise数组uploadPromises中,最后再使用await Promise.all(uploadPromises)来等待全部块上传完成之后进行返回上传成功的结果,但是同样的,也会被堵塞。

查询资料了解到:使用原生的 XMLHttpRequest 或者 fetch API,很难实现真正的并行上传,因为浏览器的限制会导致请求在某些情况下被阻塞。这可能会导致虽然同时创建了多个上传请求,但它们实际上可能会被串行处理。

代码思路:

考虑使用 Web Workers、现代的上传库(如 axios 或 fetch-with-progress)、或者其他基于 XMLHttpRequest 的库,以实现并行上传的功能,这里我使用的是axios去处理。

axios方法

async function uploadFile(file){
  // 初始化定义
  const chunkSize = 1024 * 1024 * 5;                      // 每个块的大小(这里设置为5MB)
  const totalChunks = Math.ceil(file.size / chunkSize);   // 总块数
  const uploadProgress = [];                              // 存放各分块上传进度的数组,用于求平均值

  // 创建并发上传任务
  for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
    const start = chunkIndex * chunkSize;                   // 起始位置
    const end = Math.min(start + chunkSize, file.size);     // 结束位置,判断是否大于总文件大小,拿较小的一个
    const chunk = file.slice(start, end);                   // 分割的块
    const chunkName = `${file.name}.part${chunkIndex}`      // 设置的块名,方便后端区分是哪一个块

    // 封装数据,使用append方法添加传递给后端的信息
    const formData = new FormData();
    formData.append("file", chunk, chunkName);    // 块文件
    formData.append("index", chunkIndex)          // 块索引
    formData.append("total", totalChunks)         // 总块数
    formData.append("fileName", file.name);       // 上传文件名

    const uploadPromise = axios.post("你的上传接口", formData, {
    
      // axios配置项,监听上传进度
      onUploadProgress: progressEvent => {
        const percentComplete = (progressEvent.loaded / progressEvent.total) * 100;  // 当前块的上传百分比
        uploadProgress[chunkIndex] = percentComplete;          // 将当前块的百分比更新到数组中
        const percent = calculateAverage(uploadProgress);      // 计算百分比平均值
        
        // 此处调用修改进度条UI的数值
        // ......
        
      }
      
    }).then(res => {             // 此处res为后端传递返回的数据
      if(res.status === 200){    // 若状态码成功
        if(res.finished){        // 判断是否已经全部完成
          
          // 已经全部传输完毕的操作
          // ......
          
        }
      }else{
        
        // 某个块chunkIndex上传出错
        
      }
    }).catch(error => {
      console.error(`块 ${chunkIndex + 1} 上传出错:`, error);
    });
  } 
}

// 求数组中的平均数
function calculateAverage(numbers) {
  let sum = 0;
  numbers.forEach(num => {
    sum += num;
  });
  return sum / numbers.length;
}

总结

1、使用分包上传返回进度首先要分块,使用for循环将每次请求块的起始和终止位置算出,使用 Formdata()将块数据封装起来。

2、使用axios接口上传,并配置一个onUploadProgress属性来监听返回的上传进度信息,使用数组存储各进度信息,计算出平均值,进行返回或者设置外部进度条UI的样式,并使用then监听是否已经上传完成。