
本文介绍如何使用 Fetch API 和 ReadableStream 实现可实时显示下载进度的文件下载,替代传统的 方式,解决大文件“瞬间完成、无反馈”的体验问题。
本文介绍如何使用 fetch api 和 readablestream 实现可实时显示下载进度的文件下载,替代传统的 `` 方式,解决大文件“瞬间完成、无反馈”的体验问题。
在 Web 开发中,直接通过 <a href="https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561" download></a> 触发下载虽简单,但浏览器不提供任何下载状态反馈——尤其是大文件,用户只能等待,直到整个响应体加载完毕才弹出保存对话框,极易引发误操作或体验焦虑。真正的渐进式下载需借助 fetch() 获取流式响应,并手动读取 ReadableStream 的数据块,结合 Content-Length 头计算实时进度。
以下是一个完整、可运行的进度感知下载方案:
function downloadWithProgress(https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561, progressElement) {
return new Promise((resolve, reject) => {
fetch(https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561)
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
const totalSize = Number(response.headers.get('content-length'));
if (!totalSize) {
console.warn('Warning: Content-Length header missing — progress will be estimated.');
}
const reader = response.body.getReader();
let downloadedSize = 0;
// 初始化进度元素(支持 <progress> 或自定义 DOM)
if (progressElement && 'max' in progressElement) {
progressElement.max = totalSize || 100;
progressElement.value = 0;
}
function readChunk() {
return reader.read().then(({ done, value }) => {
if (done) {
resolve(response);
return;
}
if (value) {
downloadedSize += value.length;
const percent = totalSize
? Math.min(100, Math.round((downloadedSize / totalSize) * 100))
: Math.min(100, Math.round((downloadedSize / 1000000) * 100)); // 粗略估算(MB级)
// 更新进度 UI
if (progressElement) {
if ('value' in progressElement) {
progressElement.value = totalSize ? downloadedSize : percent;
progressElement.textContent = `${percent}%`;
} else if (typeof progressElement === 'function') {
progressElement(percent, downloadedSize, totalSize);
}
}
}
return readChunk();
});
}
return readChunk();
})
.catch(reject);
});
}
// 使用示例:插入原生 <progress> 元素
const progress = document.createElement('progress');
progress.style.width = '100%';
progress.style.margin = '1rem 0';
document.body.appendChild(progress);
downloadWithProgress(myhttps://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561, progress)
.then(response => {
// ✅ 下载完成,此时可触发保存逻辑(如 Blob + https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561.createObjecthttps://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561)
return response.blob();
})
.then(blob => {
const https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561 = https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561.createObjecthttps://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561(blob);
const a = document.createElement('a');
a.href = https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561;
a.download = data.name || 'download.bin';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561.revokeObjecthttps://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561(https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561);
})
.catch(err => {
console.error('Download failed:', err);
alert(`下载失败:${err.message}`);
})
.finally(() => {
document.body.removeChild(progress);
});✅ 关键说明与注意事项:
-
Content-Length是可选头:若服务端未返回该 header(如动态生成文件、分块编码),无法获得准确总大小,此时建议改用百分比估算或切换为「已下载字节数」文本提示; -
跨域限制:确保服务端设置了
Access-Control-Expose-Headers: Content-Length,否则 JS 无法读取该 header; -
内存友好性:本方案不将全部数据缓存在内存中,而是逐块读取并丢弃(除非你显式保存
value),适合 GB 级文件; -
兼容性:
ReadableStream和response.body在现代浏览器(Chrome 43+, Firefox 65+, Edge 79+)中已稳定支持;旧版需 polyfill(如web-streams-polyfill); -
安全提示:避免对不可信 https://www.php.cn/link/886ad506e0c115cf590d18ebb6c26561 直接调用
fetch(),防止 SSRF 或恶意重定向;生产环境建议校验 MIME 类型与文件扩展名。
通过该方案,你不仅能呈现真实进度,还可拓展为暂停/恢复、限速、断点续传等高级功能——真正让文件下载从“黑盒等待”变为“可控交互”。
立即学习“Java免费学习笔记(深入)”;


















