JavaScript中用fetch下载大文件并监听进度的核心是利用Response.body的ReadableStream配合getReader()逐块读取,手动累加字节数计算百分比,需服务端返回Content-Length,通过循环read()获取Uint8Array并合并为Blob。

JavaScript 中用 fetch 下载大文件并监听进度,核心是利用 Response.body 返回的 ReadableStream,配合 getReader() 逐块读取数据,并在每次读取时累加已下载字节数。注意:fetch 本身不提供内置进度事件(如 XMLHttpRequest 的 onprogress),必须手动计算。
1. 获取流并创建 reader
发起 fetch 请求后,不要直接调用 response.blob() 或 response.arrayBuffer() —— 这会阻塞直到整个响应加载完成,无法监听进度。应先检查 response.body 是否存在且为可读流,再用 getReader() 获取流读取器:
- 确保服务端返回了
Content-Length响应头(用于计算总大小) - 使用
response.headers.get('content-length')获取总字节数(字符串,需转为 number) - 调用
response.body.getReader()得到ReadableStreamDefaultReader
2. 循环读取并累计进度
通过 reader.read() 持续读取 Uint8Array 块,每次读取后更新已下载量,触发自定义进度回调:
-
read()返回 Promise,resolve 结果为{ done: boolean, value: Uint8Array | undefined } - 当
done === true表示流结束 -
value?.length即本次读取的字节数,累加即可得到当前已下载量 - 用
Math.round((loaded / total) * 100)计算百分比,避免浮点误差
3. 处理错误与终止逻辑
流读取过程可能因网络中断、用户取消或解析失败而中止,需妥善处理:
- 捕获
reader.read()的 rejected Promise(如网络错误、流中断) - 调用
reader.cancel()可主动中止读取(例如用户点击“取消”) - 注意:
cancel()后再调用read()会抛错,应设标志位避免重复操作 - 最终将所有
Uint8Array合并为完整 ArrayBuffer(可用new Blob([chunks])直接构造文件)
4. 实际调用示例(简化版)
以下是一个可直接运行的轻量封装:
async function downloadWithProgress(url, onProgress) {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
<p>const contentLength = response.headers.get('content-length');
const total = contentLength ? parseInt(contentLength, 10) : 0;
const reader = response.body.getReader();
let loaded = 0;
const chunks = [];</p><p>while (true) {
const { done, value } = await reader.read();
if (done) break;</p><pre class='brush:php;toolbar:false;'>chunks.push(value);
loaded += value.length;
if (typeof onProgress === 'function') {
const percent = total ? Math.round((loaded / total) * 100) : 0;
onProgress({ loaded, total, percent });
}}
const blob = new Blob(chunks); return blob; // 或 URL.createObjectURL(blob),供 a 标签下载 }
// 使用:
downloadWithProgress('/large-file.zip', ({ percent }) => {
console.log(下载中:${percent}%);
});


















