Fetch无法直接监听progress事件,但可通过ReadableStream读取响应体、累加字节并结合Content-Length计算下载进度,需服务端暴露该响应头,动态内容则无法准确预估。

Fetch 本身不支持像 XMLHttpRequest 那样直接监听 progress 事件,但可以通过 ReadableStream + decoder + 计数累计 的方式手动计算下载进度,从而模拟出类似 XHR 的进度条效果。
核心思路:流式读取 + 实时字节统计
利用 response.body.getReader() 获取可读流,每次调用 reader.read() 得到一块 Uint8Array 数据,累加其 .length,再除以已知的总大小(Content-Length),即可算出当前下载百分比。
- 需要服务端返回
Content-Length响应头(否则无法预知总大小) - 若资源是动态生成或分块传输(如 chunked encoding),则无法准确预估总大小,此时只能显示“加载中”或使用估算策略(如按 chunk 数粗略递增)
- 推荐配合
AbortController实现取消,避免 reader 持有失效引用
基础实现:带进度回调的 fetchWithProgress
下面是一个轻量封装,接受 URL 和一个 onProgress 回调函数:
async function fetchWithProgress(url, onProgress) {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const contentLength = response.headers.get('content-length');
const total = contentLength ? parseInt(contentLength, 10) : null;
const reader = response.body.getReader();
let loaded = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
loaded += value.length;
if (onProgress && total) {
onProgress(loaded, total); // 例如:onProgress(12345, 1048576)
}
}
const chunks = [];
reader.releaseLock();
return new Response(new ReadableStream({
start(controller) {
controller.enqueue(new Uint8Array([])); // 占位,实际数据已由上面读完
controller.close();
}
}));
}
⚠️ 注意:上述代码仅演示进度逻辑;若需完整响应体(如 JSON 或 Blob),应在循环中收集 value 到 chunks 数组,最后用 new Blob(chunks) 或 new Response(...) 构造新响应。
实用增强:支持 JSON / Blob 解析 + 取消控制
更贴近生产环境的写法会整合解析与中断能力:
- 用
AbortController控制整个 fetch 和 reader - 把所有
Uint8Array合并为完整 ArrayBuffer,再转成 JSON 字符串或 Blob - 在
onProgress中传入{ loaded, total, percent: Math.round((loaded/total)*100) } - 对无
Content-Length的情况降级处理(如只触发一次onProgress(0, 0)表示开始,结束时不传进度)
对比 XMLHttpRequest 的关键差异
虽然效果相似,但底层机制不同:
- XHR 的
progress是浏览器自动触发,频率由网络栈决定;Fetch 进度依赖你主动调用reader.read(),节奏可控但需手动管理流 - XHR 支持上传进度(
upload.onprogress),Fetch 暂无等效原生方案(需借助FormData分块构造或第三方库) - Fetch 流式处理天然适合大文件、视频分片、实时日志等场景,而 XHR 更适合传统表单提交和小响应体
不复杂但容易忽略:确保服务端开了 CORS 并允许 Content-Length 暴露(Access-Control-Expose-Headers: Content-Length),否则 JS 读不到该 header。

















