Appearance
大文件上传
对文件做切片:将一个文件拆分成多个文件做请求
通知服务器合并切片:当所有切片上传完毕, 通知服务器合并切片
控制多个请求的并发量:控制同时上传切片请求的数量, 防止浏览器卡死
做断点续传:当有切片传输失败,需要对这些切片做处理,让它重新发送
切片
html
<body>
<input type="file" id="fileInput" />
<button id="uploadBtn">上传</button>
</body>
1. 读取文件
js
// 要传输的文件
var file = null;
// 获取上传完的文件
document.getElementById("fileInput").onchange = function (e) {
file = e.target.files[0];
};
2. 对文件进行切片
文件 FIle
对象是 Blob
对象的子类,通过 slice
方法,可以对二进制文件进行拆分
js
function createSlice(file) {
if (!file) return;
let size = 1024 * 1024 * 2; //2MB 定义每份切片大小
let fileChunks = [];
let index = 0; //切片序号
// 将切片的文件放入数组容器中;
for (let cur = 0; cur < file.size; cur += size) {
fileChunks.push({
hash: index++,
chunk: file.slice(cur, cur + size),
});
}
return fileChunks;
}
3. 创建请求上传切片
js
document.getElementById("uploadBtn").onclick = async () => {
// 创建切片
const fileChunks = createSlice(file);
// 为每个切片创建传送请求
const uploadList = fileChunks.map((item, index) => {
let formData = new FormData();
formData.append("filename", file.name);
formData.append("hash", item.hash);
formData.append("chunk", item.chunk);
return axios({
method: "post",
url: "/upload",
data: formData,
});
});
// 等所有切片传输完毕
await Promise.all(uploadList);
// 通知服务器切片传输完毕, 可以合并切片
await axios({
method: "get",
url: "/merge",
params: {
filename: file.name,
},
});
console.log("上传完成");
};
控制并发
建立并发池, 设立最大并发量
js
document.getElementById("uploadBtn").onclick = async () => {
// 创建切片
const fileChunks = createSlice(file);
let pool = []; //并发池
let max = 3; //最大并发量
for (let i = 0; i < fileChunks.length; i++) {
let item = fileChunks[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);
});
// 将切片任务塞进并发池执行
pool.push(task);
if (pool.length === max) {
//并发池任务已满时, 等待释放空位
await Promise.race(pool);
}
}
//通知服务器合并切片
await axios({
method: "get",
url: "/merge",
params: {
filename: file.name,
},
});
console.log("上传完成");
};
断点续传
建立上传失败列表, 重复上传,直至所有切片上传成功
js
async function uploadFileChunks(list) {
if (list.length === 0) {
//所有任务完成,合并切片
axios({
method: "get",
url: "/merge",
params: {
filename: file.name,
},
});
console.log("上传完成");
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);
}
}
}
调用:
js
document.getElementById("uploadBtn").onclick = () => {
// 创建切片
const fileChunks = createSlice(file);
// 上传切片
uploadFileChunks(fileChunks);
};