原生input[type="file"]不支持断点续传,因其无法控制分片、进度和暂停恢复;需结合file api、blob.slice()切片、fetch手动上传,并依赖服务端校验已传分片。

为什么直接用 input[type="file"] 无法支持断点续传
浏览器原生的 <input type="file"> 只提供文件选择和一次性读取能力,不暴露文件分片、上传进度、暂停/恢复等底层控制权。断点续传依赖对文件切片、每片独立请求、服务端校验与合并的支持,必须绕过原生表单提交,改用 File API + fetch 或 XMLHttpRequest 手动管理。
常见错误现象:upload failed: TypeError: Failed to execute 'fetch' on 'Window': Request body is not a stream —— 这通常是因为把整个大 File 对象直接传给 fetch,而没用 slice() 切出 Blob 子块。
- 必须用
file.slice(start, end)每次生成独立Blob,再构造FormData或直接作为fetchbody - 服务端需返回已上传分片列表(如
["0", "1", "3"]),前端据此跳过已传分片 - 不能依赖
onload或then()隐式顺序——需显式维护分片索引、重试计数、失败队列
如何用 Blob.slice() 安全切分 2GB 文件而不卡死主线程
直接循环调用 file.slice() 会阻塞渲染,尤其在低配设备上导致界面冻结。关键不是“能不能切”,而是“怎么切得轻量又可控”。
实操建议:
- 避免一次性生成全部分片:用
for循环 +await new Promise(r => setTimeout(r, 0))插入微任务间隙,让 UI 保持响应 - 分片大小建议设为
4 * 1024 * 1024(4MB):太小(如 100KB)增加 HTTP 开销;太大(如 50MB)易因单片超时失败而重传整块 -
file.slice()返回新Blob,但不会复制内存——它只是视图引用,开销极低;真正耗时的是后续读取或上传 - 别用
FileReader.readAsArrayBuffer()全量读取大文件:改用stream().getReader()+Uint8Array流式处理(仅 Chromium 系支持)
上传失败后如何准确恢复到上一个成功分片位置
断点续传的核心不是“记住第几片”,而是“服务端确认哪几片已写入磁盘”。前端本地缓存(如 localStorage)不可靠:用户清缓存、换设备、或多标签并发上传都会导致状态错乱。
正确做法:
- 每次上传前先发
GET /upload/status?file_id=xxx请求,服务端返回已存分片索引数组(如[0,1,2,4]) - 前端比对本地分片总数与服务端已传列表,只发起未上传或上传失败(HTTP 500/timeout)的分片请求
- 每个分片请求必须带唯一
chunkIndex和totalChunks,服务端据此校验是否重复、越界或缺失 - 上传成功后立即更新本地
sessionStorage(非localStorage),仅用于本次页面生命周期内的快速恢复
容易踩的坑:if (response.status === 200) { uploadNextChunk() } —— 这忽略网络抖动导致的“请求发出但响应丢失”,应结合服务端幂等性设计,允许重复上传同一片。
如何显示真实进度且不被分片重试干扰
单纯累加成功分片数 / 总分片数会误导用户:比如第 5 片失败重试中,进度条却卡在 4/10;或者第 3 片重试 3 次才成功,进度反复倒退。
推荐方案:
- 用两个指标分离展示:
已确认写入(服务端 commit 后返回) +当前传输中(正在 fetch 的分片) - 进度值 =
(confirmedCount + uploadingCount * 0.5) / totalChunks,让用户感知“正在努力,没卡住” - 每个分片上传时记录
startTime,若耗时 > 30s 主动 abort 并标记为待重试,避免单片阻塞整体 - 禁用上传按钮但保留“暂停”按钮,点击后调用
abortController.abort()并保存当前confirmedCount
真实场景里,网络波动比代码逻辑更难预测。服务端是否支持秒级分片校验、CDN 是否缓存了 206 响应、客户端是否启用了 QUIC——这些都会影响“断点”是否真能“续”。别只盯着前端重试次数,先确保服务端返回的 ETag 或 Content-MD5 能精准识别重复分片。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











