
bug收集:专门解决与收集bug的网站
网址:www.bugshouji.com
今日分享:JS 上传大文件的解决思路
1. 文件切片
把一个大文件转换成二进制内容,然后按照一个固定的大小对二进制内容进行切割,得到多个小文件,然后循环上传所有的小文件。在js中,文件File对象是Blob对象的子类,可以使用 slice() 方法完成对文件的切割;
获取文件对象( e.target.files[0])
// 选中的文件
var file = null
// 选择文件
document.getElementById('fileInput').onchange = function
({ target: { files } }) {
file = files[0]; //文件对象
}
let size = 1024 * 50; //50KB 切片大小创建切片(file.slice)
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. 文件合并
当所有小文件上传完成,调用接口通知后端把所有的文件按编号进行合并,组成大文件;
if (list.length === 0) {
//所有任务完成,合并切片
await axios({
method: 'get',
url: '/merge',
params: {
filename: file.name
}
});
console.log('上传完成')
return
}
3. 并发控制
结合Promise.race和异步函数实现,限制多个请求同时并发的数量,防止浏览器内存溢出;
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. 断点续传
把所有上传失败的小文件加入一个数组里面,在所有小文件都上传结束(成功和失败都算结束)之后再上传一次上传失败了的小文件,反复执行这一步,直到所有小文件都上传成功,可以通过递归实现。
// 上传切片
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)
}
})完整代码:
<!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
苟有恒 , 何必三更眠五更起