
本文介绍如何使用 Fetch API 和 ReadableStream 实现真正可监控进度的文件下载,替代传统的 方式,解决大文件“瞬间完成、无进度反馈”的体验问题。
本文介绍如何使用 fetch api 和 readablestream 实现真正可监控进度的文件下载,替代传统的 `` 方式,解决大文件“瞬间完成、无进度反馈”的体验问题。
传统通过动态创建 <a></a> 标签并触发 click() 的下载方式(如 link.href = url; link.click())完全依赖浏览器原生行为:它不暴露下载过程,无法获取已接收字节数,因此无法显示实时进度——用户只能等待,直到整个响应体加载完毕后才弹出保存对话框,造成“卡顿感”和不可控体验。
要实现真实、可感知的下载进度,必须绕过 <a download></a>,改用 fetch() 获取响应流(Response.body),再通过 ReadableStream.getReader() 逐块读取数据,并结合 Content-Length 头计算当前下载百分比。
以下是一个完整、可运行的进度驱动下载示例:
掌握 ES6+ 特性,包括 async/await、解构、扩展运算符、箭头函数、Promise、模块、迭代器、生成器以及函数式编程模式,编写简洁高效的 Java
<!-- 页面中需存在一个进度指示器 --> <progress id="download-progress" value="0" max="100">0%</progress> <div id="progress-text">准备下载...</div>
async function downloadWithProgress(url, filename) {
const progressElem = document.getElementById('download-progress');
const textElem = document.getElementById('progress-text');
try {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
// 获取总大小(注意:需服务端支持 CORS 并暴露 Content-Length)
const totalSize = Number(response.headers.get('content-length'));
if (!totalSize) {
console.warn('⚠️ 未返回 Content-Length,无法计算精确进度(可能因 CORS 或服务器配置)');
textElem.textContent = '未知大小,正在下载...';
}
const reader = response.body.getReader();
let downloadedSize = 0;
// 创建 Blob 构造器,用于最终拼接完整文件
const chunks = [];
const updateProgress = (loaded) => {
if (totalSize && progressElem) {
const percent = Math.min(100, Math.floor((loaded / totalSize) * 100));
progressElem.value = percent;
textElem.textContent = `下载中:${percent}%`;
}
};
// 流式读取
while (true) {
const { done, value } = await reader.read();
if (done) break;
chunks.push(value);
downloadedSize += value.length;
updateProgress(downloadedSize);
}
// 合并所有 chunk 为 Blob
const blob = new Blob(chunks, { type: response.headers.get('content-type') || 'application/octet-stream' });
// 触发下载(使用 URL.createObjectURL 避免内存泄漏)
const blobUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = blobUrl;
a.download = filename || 'download.bin';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl); // 及时释放对象 URL
textElem.textContent = '✅ 下载完成!';
setTimeout(() => {
progressElem.value = 0;
textElem.textContent = '准备下载...';
}, 1500);
} catch (err) {
console.error('❌ 下载失败:', err);
textElem.textContent = `❌ 下载失败:${err.message}`;
}
}
// 使用示例
// downloadWithProgress('/api/large-file.zip', 'report.zip');✅ 关键要点与注意事项:
立即学习“Java免费学习笔记(深入)”;
-
CORS 要求:服务端必须在响应头中设置
Access-Control-Expose-Headers: Content-Length,否则 JavaScript 无法读取Content-Length; -
流兼容性:
response.body.getReader()在现代浏览器(Chrome 43+、Firefox 65+、Edge 79+、Safari 16.4+)中稳定支持,旧版 Safari 需降级处理; -
内存优化:避免将全部二进制数据缓存在变量中;本例使用
chunks数组暂存Uint8Array,最终一次性构造Blob,平衡性能与可控性; -
用户体验增强:可进一步集成取消按钮(通过
AbortController)、限速控制、断点续传(需服务端配合Range请求)等高级功能。
该方案不仅解决了“进度不可见”问题,更将文件下载从黑盒操作转变为可编程、可监控、可交互的前端流程,是构建专业级文件管理界面的必备实践。

















