前端切片与并发控制

前端大文件切片、并发上传、暂停恢复与重试机制

#type / concept #status / evergreen #tech / dev / frontend #platform / browser

[!info] related notes

前端切片与并发控制

一句话定义

前端切片是利用 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 一致可继续上传
创建于 2026/3/28 更新于 2026/5/27