首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >js 大文件上传的思路

js 大文件上传的思路

作者头像
用户9914333
发布2022-12-14 18:13:43
发布2022-12-14 18:13:43
8.2K0
举报
文章被收录于专栏:bug收集bug收集

bug收集:专门解决与收集bug的网站

网址:www.bugshouji.com

今日分享:JS 上传大文件的解决思路

1. 文件切片

把一个大文件转换成二进制内容,然后按照一个固定的大小对二进制内容进行切割,得到多个小文件,然后循环上传所有的小文件。在js中,文件File对象是Blob对象的子类,可以使用 slice() 方法完成对文件的切割;

获取文件对象( e.target.files[0])

代码语言:javascript
复制
// 选中的文件
  var file = null
  // 选择文件
  document.getElementById('fileInput').onchange = function
   ({ target: { files } }) {
    file = files[0]; //文件对象
  }
  let size = 1024 * 50; //50KB 切片大小

创建切片(file.slice)

代码语言:javascript
复制
let fileChunks = []
    let index = 0 //切片序号
    for (let cur = 0; cur < file.size; cur += size) {
      fileChunks.push({
        hash: index++,
        chunk: file.slice(cur, cur + size)
      })

2. 文件合并

当所有小文件上传完成,调用接口通知后端把所有的文件按编号进行合并,组成大文件;

代码语言:javascript
复制
if (list.length === 0) {
        //所有任务完成,合并切片
        await axios({
          method: 'get',
          url: '/merge',
          params: {
            filename: file.name
          }
        });
        console.log('上传完成')
        return
      }

3. 并发控制

结合Promise.race和异步函数实现,限制多个请求同时并发的数量,防止浏览器内存溢出;

代码语言:javascript
复制
let pool = []//并发池
      let max = 3 //最大并发量
      let finish = 0//完成的数量
      let failList = []//失败的列表
      for (let i = 0; i < list.length; i++) {
        let item = list[i]
        let formData = new FormData()
        formData.append('filename', file.name)
        formData.append('hash', item.hash)
        formData.append('chunk', item.chunk)
        // 上传切片
        let task = axios({
          method: 'post',
          url: '/upload',
          data: formData
        })
        task.then((data) => {
          //请求结束后将该Promise任务从并发池中移除
          let index = pool.findIndex(t => t === task)
          pool.splice(index)
        }).catch(() => {
          failList.push(item)
        }).finally(() => {
          finish++
          //所有请求都请求完成
          if (finish === list.length) {
            uploadFileChunks(failList)
          }
        })
        pool.push(task)
        if (pool.length === max) {
          //每当并发池跑完一个任务,就再塞入一个任务
          await Promise.race(pool)
        }
      }
    }

4. 断点续传

把所有上传失败的小文件加入一个数组里面,在所有小文件都上传结束(成功和失败都算结束)之后再上传一次上传失败了的小文件,反复执行这一步,直到所有小文件都上传成功,可以通过递归实现。

代码语言:javascript
复制
// 上传切片
        let task = axios({
          method: 'post',
          url: '/upload',
          data: formData
        })
        task.then((data) => {
          //请求结束后将该Promise任务从并发池中移除
          let index = pool.findIndex(t => t === task)
          pool.splice(index)
        }).catch(() => {
          failList.push(item)
        }).finally(() => {
          finish++
          //所有请求都请求完成
          if (finish === list.length) {
            uploadFileChunks(failList)
          }
        })

完整代码:

代码语言:javascript
复制
<!DOCTYPE html>
<html lang="en">
 
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=s, initial-scale=1.0">
  <title>Document</title>
  <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.24.0/axios.min.js"></script>
</head>
 
<body>
  <input type="file" id="fileInput">
  <button id="uploadBtn">上传</button>
</body>
<script>
  // 请求基准地址
  axios.defaults.baseURL = 'http://localhost:3000'
  // 选中的文件
  var file = null
  // 选择文件
  document.getElementById('fileInput').onchange = function ({ target: { files } }) {
    file = files[0]
  }
  // 开始上传
  document.getElementById('uploadBtn').onclick = function () {
    if (!file) return
    // 创建切片
    // let size = 1024 * 1024 * 10; //10MB 切片大小
    let size = 1024 * 50; //50KB 切片大小
    let fileChunks = []
    let index = 0 //切片序号
    for (let cur = 0; cur < file.size; cur += size) {
      fileChunks.push({
        hash: index++,
        chunk: file.slice(cur, cur + size)
      })
    }
    // 控制并发和断点续传
    const uploadFileChunks = async function (list) {
      if (list.length === 0) {
        //所有任务完成,合并切片
        await axios({
          method: 'get',
          url: '/merge',
          params: {
            filename: file.name
          }
        });
        console.log('上传完成')
        // TODO 
        return
      }
      let pool = []//并发池
      let max = 3 //最大并发量
      let finish = 0//完成的数量
      let failList = []//失败的列表
      for (let i = 0; i < list.length; i++) {
        let item = list[i]
        let formData = new FormData()
        formData.append('filename', file.name)
        formData.append('hash', item.hash)
        formData.append('chunk', item.chunk)
        // 上传切片
        let task = axios({
          method: 'post',
          url: '/upload',
          data: formData
        })
        task.then((data) => {
          //请求结束后将该Promise任务从并发池中移除
          let index = pool.findIndex(t => t === task)
          pool.splice(index)
        }).catch(() => {
          failList.push(item)
        }).finally(() => {
          finish++
          //所有请求都请求完成
          if (finish === list.length) {
            uploadFileChunks(failList)
          }
        })
        pool.push(task)
        if (pool.length === max) {
          //每当并发池跑完一个任务,就再塞入一个任务
          await Promise.race(pool)
        }
      }
    }
    uploadFileChunks(fileChunks)
 
  }
</script>
 
</html>

参考

https://blog.csdn.net/yyw_1972088267/article/details/126094408


苟有恒 , 何必三更眠五更起

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-11-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 bug收集 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档