JavaScript中Fetch API上传文件并监控进度需用ReadableStream包装文件流,逐块读取并实时上报进度,服务端须支持流式接收,旧浏览器应降级使用XMLHttpRequest。

在 JavaScript 中使用 Fetch API 上传文件并监控进度,核心是利用 ReadableStream + TransformStream(或手动分块读取)配合 AbortController 和事件监听,但需注意:**原生 Fetch 不直接暴露上传进度事件**,必须通过包装请求体(如用 ReadableStream 构造自定义流)来实现边读边上报。
用 ReadableStream 包装文件并实时计算进度
这是目前最标准、兼容性较好(Chrome 109+、Firefox 117+、Safari 16.4+)的方式。原理是将 File 或 Blob 转为可读流,逐块读取并累加已上传字节数。
- 创建一个返回
ReadableStream的函数,内部用file.stream().getReader()读取文件内容 - 每次
reader.read()返回 chunk 后,立即通过controller.enqueue(chunk)推入新流,并更新进度值(如uploaded += chunk.length) - 将该流作为
fetch()的body,同时在读取循环中触发自定义进度回调(如onProgress({ loaded, total }))
完整示例代码(含进度回调)
以下是一个可直接运行的封装函数:
async function uploadWithProgress(file, url, onProgress) {
const reader = file.stream().getReader();
const { readable, writable } = new TransformStream();
<p>const writer = writable.getWriter();
let loaded = 0;
const total = file.size;</p><p>async function pump() {
const { done, value } = await reader.read();
if (done) {
await writer.close();
return;
}</p><pre class="brush:php;toolbar:false;"><pre class="brush:php;toolbar:false;">loaded += value.byteLength;
onProgress({ loaded, total, percent: Math.round((loaded / total) * 100) });
await writer.write(value);
await pump();}
立即学习“Java免费学习笔记(深入)”;
pump().catch(console.error);
const response = await fetch(url, { method: 'POST', body: readable, headers: { // 注意:使用流时不能设 Content-Length,后端需支持分块传输或流式解析 'Content-Type': file.type || 'application/octet-stream', }, });
return response; }
// 使用示例 const fileInput = document.querySelector('#file'); fileInput.addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return;
await uploadWithProgress(file, '/upload', ({ loaded, total, percent }) => {
console.log(上传中:${percent}% (${loaded}/${total} bytes));
// 可更新进度条 DOM,例如:
// progressBar.style.width = <code>${percent}%;
});
});
注意事项与替代方案
实际项目中需留意几个关键点:
-
服务端必须能接收流式请求:Node.js(Express 需搭配
raw-body或busboy)、Nginx 需配置client_max_body_size并禁用缓冲(proxy_buffering off),否则可能等待整个流结束才处理 -
不支持传统表单字段混合上传:若需同时传文本字段(如 title、desc),建议改用
FormData+XMLHttpRequest(它原生支持upload.onprogress),或在服务端约定用 JSON 元数据 + 文件流分离传输 -
兼容性兜底方案:如需支持旧浏览器,仍推荐
XMLHttpRequest;现代项目可结合AbortController实现取消上传
简单取消上传(配合 AbortController)
可在上传过程中随时中断:
- 创建
const controller = new AbortController() - 将
signal: controller.signal传入fetch()选项 - 调用
controller.abort()即可中止请求(会拒绝 Promise 并抛出AbortError)
注意:中止后流读取也会被取消,需在 pump() 中捕获 reader.read() 抛出的异常。


















