js之分片上传大文件

404 阅读2分钟

前端上传文件的过程中,往往会遇到大文件的情况,这时,若直接进行上传,则请求传输数据时间过长,往往超过网关设置的超时时间,从而导致文件上传失败,这个时候,就需要对大文件进行分片上传处理

前置条件:后端配合进行分片上传,提供分片上传接口,后端拼接文件进行存储
前置数据:file(大文件二进制数据file类型)、size(分片大小)
流程:
  1. 文件分片器:对file进行分片,得到片段队列chunkList,即任务队列
  2. 编写一个单片文件上传器:上传单片文件
  3. 编写任务调度器:从任务队列中取出文件片段fileChunk,调取第二步的单片文件上传器,记录上传结果。对于上传失败的片段进行重复上传,并记录重复上传次数;当重复上传次数大于预定次数时,暂停后续上传任务
  4. 启动文件分片器,分片器运行完毕后启动任务调度器
核心函数:
  1. 文件分片器
createFileChunk(file, size = 5 * 1024 * 1024) {
      const fileChunkList = []
      let count = 0
      let index = 1
      while (count < file.size) {
        fileChunkList.push({
          file: file.slice(count, count + size),
          index,
        })
        index++
        count += size
      }
      return fileChunkList
},
  1. 单片文件上传器 uploadChunkFile(chunk) 根据后端单片文件上传接口,编写即可,传入单片文件二进制数据

  2. 任务调度器 返回一个promise对象

sendRequest(fileChunkList) {
      let finished = 0 //成功片数
      const total = fileChunkList.length //总共分片数
      const retryArr = [] //各片重传次数记录数组
      const chunkRetry = 3 //重传预设次数
      const errorTag = false //单片上传失败:重传次数用尽,设为true,后续任务暂停
      let etagList = [] //上传成功后,接口返回数据记录
      return new Promise((resolve, reject) => {
        const handel = () => {
          if (fileChunkList.length && fileChunkList.length > 0) {
            //任务出栈
            const chunk = fileChunkList.shift()
            const index = chunk.index
            uploadChunkFile(chunk)
              .then((res) => {
                //片上传成功
                etagList.push(res)
                finished++
                if (!errorTag) {
                  //有一项失败,则不用继续上传,暂停请求
                  handel()
                }
              })
              .catch((e) => {
                if (typeof retryArr[index] !== 'number') {
                  //初始化该块的重试次数0
                  retryArr[index] = 0
                }
                retryArr[index]++
                if (retryArr[index] > chunkRetry) {
                  errorTag = true
                  reject('单片文件上传失败', retryArr)
                  return //不往下进行重传操作
                }
                //重传,加入队列
                fileChunkList.push(chunk)

                // 当前任务完成,开启下一个任务
                handel()
              })
          }
          // 全部片段上传成功,将返回数据记录数组抛出
          if (finished >= total) {
            resolve(etagList)
          }
        }
        // 控制并发,同时进行3项片段上传任务
        for (let i = 0; i < 3; i++) {
          handel()
        }
      })
    },