JavaScript无法直接监听blob/arraybuffer下载进度,需用Fetch+ReadableStream流式读取并结合Content-Length计算进度,或用XHR(限制多),且依赖后端正确设置Content-Length、禁用gzip、暴露响应头。

JavaScript 本身不能直接监听后端返回的二进制文件流下载进度(比如 responseType: 'blob' 或 'arraybuffer'),因为浏览器在收到完整响应前不会触发 onload,而 onprogress 在大多数情况下对非流式响应无效。真正能获取实时下载进度的方式,是使用 Fetch API 配合 ReadableStream + 进度计算,前提是后端支持分块传输(Transfer-Encoding: chunked)并正确设置 Content-Length(或至少不隐藏它)。
1. 使用 Fetch + Response.body 流式读取(推荐)
这是目前最可靠、可精确计算下载进度的方式,适用于现代浏览器(Chrome 68+、Firefox 65+、Edge 79+):
- 用
fetch()发起请求,不设responseType,保持默认(即流式响应) - 通过
response.body.getReader()获取流读取器 - 每次
reader.read()返回一个{ done, value },value是Uint8Array,长度即本次接收字节数 - 累加已读字节数,除以
response.headers.get('content-length')得到进度百分比(若服务端返回了该 header)
示例代码:
async function downloadWithProgress(url) {
const response = await fetch(url);
const contentLength = response.headers.get('content-length');
const total = contentLength ? parseInt(contentLength, 10) : null;
const reader = response.body.getReader();
let loaded = 0;
<p>while (true) {
const { done, value } = await reader.read();
if (done) break;
loaded += value.length;</p><pre class='brush:php;toolbar:false;'>const progress = total ? Math.round((loaded / total) * 100) : null;
console.log(`下载中:${loaded} / ${total || '未知'} 字节 (${progress}%)`);}
立即学习“Java免费学习笔记(深入)”;
OpenJDK 24 Windows x64 官方参考实现(24+36),适合 Windows 环境进行 Java 开发、编译、运行和版本兼容性验证。
reader.releaseLock(); }
2. 使用 XMLHttpRequest(兼容旧版,但限制多)
XHR 的 onprogress 事件仅在响应头包含 Content-Length 且浏览器支持时才有效;对 blob/arraybuffer 响应类型,event.loaded 和 event.total 可能为 0 或不准确:
- 必须设置
xhr.responseType = 'arraybuffer'(不能是 'blob',否则部分浏览器不触发 progress) - 需在
open()后、send()前监听onprogress - 仅当服务端明确返回
Content-Length,event.total才有意义 - 注意:Safari 对 arraybuffer + progress 支持不稳定,建议降级为流式 fetch
3. 后端配合要点(关键!)
前端能否拿到进度,一半取决于后端配置:
- 务必设置
Content-Length响应头(静态文件或已知大小时最稳妥) - 如为动态生成文件(如 ZIP、PDF 导出),尽量预估或分块输出,并启用
Transfer-Encoding: chunked - 避免使用
gzip压缩(会干扰 Content-Length 和流读取逻辑),或确保解压由前端完成(复杂且不推荐) - 跨域请求需确保
Access-Control-Expose-Headers: Content-Length
4. 进度 UI 更新与 Blob 构造(完成后)
流式读取结束后,需把所有 Uint8Array 合并为完整数据,再转成 Blob 并触发下载:
- 用
new Uint8Array(totalSize)预分配,或用Uint8Array.from(chunks.flat())拼接(小文件) - 构造
new Blob([uint8Array], { type: response.headers.get('content-type') || 'application/octet-stream' }) - 用
URL.createObjectURL(blob)创建临时链接并模拟点击下载
注意:不要在流读取过程中频繁创建 Blob,否则影响性能。

















