Fetch API 无法直接监听下载进度,但可通过 Response.body 的 ReadableStream 与 getReader() 逐块读取并累加字节数实现进度监控;需依赖服务端返回 Content-Length 响应头计算百分比,且 CORS 下需暴露该头;示例函数支持取消、错误处理及节流更新 UI。

Fetch API 本身不直接提供下载进度事件(如 onprogress),但可以通过读取 Response.body 的 ReadableStream,配合 getReader() 逐块读取数据,并在每次读取后累加已接收字节数,从而实现下载进度与百分比的监控。
获取 Content-Length 并计算总大小
要显示百分比,需知道文件总大小。理想情况下服务端响应头中包含 Content-Length:
- 用
response.headers.get('content-length')获取总字节数(字符串,需转为Number) - 若服务端未返回该头(如分块传输、流式生成内容),则无法预知总大小,只能显示“已下载 X 字节”,无法计算百分比
- 注意:CORS 环境下需服务端显式暴露该头:
Access-Control-Expose-Headers: content-length
用 ReadableStream.reader 逐块读取并累加进度
调用 response.body.getReader() 获取流读取器,循环 read() 直到 done === true,每次读取 value(Uint8Array)后更新已下载量:
-
reader.read()返回 Promise,解构出{ done, value } -
value?.length即本次读取的字节数,累加到loaded - 用
loaded / total计算当前百分比(保留 1 位小数更友好) - 推荐使用
requestIdleCallback或节流方式更新 UI,避免频繁渲染
完整可运行示例(含错误处理与取消支持)
以下是一个带进度回调、支持取消、兼容常见场景的封装函数:
立即学习“Java免费学习笔记(深入)”;
async function fetchWithProgress(url, onProgress = () => {}) {
const controller = new AbortController();
const signal = controller.signal;
<p>try {
const response = await fetch(url, { signal });</p><pre class='brush:php;toolbar:false;'>if (!response.ok) throw new Error(`HTTP ${response.status}`);
const total = Number(response.headers.get('content-length')) || null;
const reader = response.body.getReader();
let loaded = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
loaded += value.length;
const progress = total ? Math.round((loaded / total) * 100) : null;
onProgress({ loaded, total, progress, percent: progress ? `${progress}%` : '未知大小' });
}
const chunks = [];
reader.releaseLock();
const allChunks = await response.arrayBuffer();
return allChunks; // 或用 new Blob([allChunks]) 构造文件} catch (err) { if (err.name === 'AbortError') console.log('下载已取消'); throw err; } }
// 使用示例:
fetchWithProgress('/large-file.zip', ({ percent }) => {
document.getElementById('progress').textContent = 下载中:${percent};
});
注意事项与替代方案
实际使用中需留意几个关键点:
- Chrome/Firefox/Edge 支持良好,Safari 对大文件流读取稳定性稍弱,建议加异常兜底
- 若需同时支持上传进度,仍需用
XMLHttpRequest(它原生支持upload.onprogress) - 对超大文件,避免把全部 chunk 存入内存;可边读边写入
WritableStream(如 Service Worker 缓存或文件系统) - 部分 CDN 或代理可能移除
Content-Length,此时可考虑服务端改用Content-Range配合固定分片逻辑


















