前端切片与并发控制
前端大文件切片、并发上传、暂停恢复与重试机制
#type / concept
#status / evergreen
#tech / dev / frontend
#platform / browser
[!info] related notes
- 所属 MOC: 前端工程化 MOC
- 前置概念: 大文件传输概述
- 并列概念: 后端状态管理与分块合并
- 关系笔记: 大文件传输系统设计
前端切片与并发控制
一句话定义
前端切片是利用 File.slice() 将大文件拆分成固定大小的 chunk,通过并发队列控制上传节奏,实现可恢复、可暂停的分块上传。
核心内容
文件切片
浏览器 File/Blob 支持 slice 方法:
function createChunks(file, chunkSize) {
const chunks = [];
let start = 0;
let index = 0;
while (start < file.size) {
const end = Math.min(start + chunkSize, file.size);
chunks.push({
index,
start,
end,
blob: file.slice(start, end),
});
start = end;
index++;
}
return chunks;
}
例如 1GB 文件、10MB chunkSize,会得到约 103 个 chunk。
并发上传控制
前端不会一个一个传,而是控制并发(通常 3-6 个):
async function uploadChunks(chunks, concurrency = 4) {
const queue = [...chunks];
const workers = new Array(concurrency).fill(null).map(async () => {
while (queue.length) {
const chunk = queue.shift();
if (!chunk) break;
await uploadSingleChunk(chunk);
}
});
await Promise.all(workers);
}
为什么限并发:
- 并发太高会打爆服务端
- 浏览器有连接数限制
- 可能触发网关限流
- 实际吞吐反而下降
单个 chunk 上传
每个 chunk 通常携带:
uploadId:上传任务唯一标识chunkIndex:分块编号chunkSize:分块大小totalChunks:总分块数chunkHash:分块哈希(可选)- 二进制内容
async function uploadSingleChunk(chunk, uploadId) {
const formData = new FormData();
formData.append("uploadId", uploadId);
formData.append("chunkIndex", String(chunk.index));
formData.append("file", chunk.blob);
await fetch("/upload/chunk", {
method: "POST",
body: formData,
});
}
暂停与恢复
暂停通过 AbortController 中止请求:
const controller = new AbortController();
fetch("/upload/chunk", {
method: "POST",
body: formData,
signal: controller.signal,
});
// 暂停
controller.abort();
恢复时查询已上传 chunk,继续剩余部分。
重试机制
单个 chunk 失败不应导致整任务失败:
- 单 chunk 最多重试 3 次
- 指数退避(立即、1s、3s)
- 网络断开后等待恢复
- 某块持续失败则整任务标红
边界与易混淆点
- chunk 编号:要明确从 0 还是 1 开始,最后一块是否允许小于 chunkSize
- 进度条:上传 chunk 阶段占 0-95%,服务端处理占 95-100%
- File 对象刷新后丢失:浏览器刷新后原 File 对象没了,需用户重新选择同一文件,但因 hash 一致可继续上传