主题
下载进度
下载进度和上传进度很像,核心都是“前端能不能知道已经传输了多少数据”。
什么时候需要下载进度
更常见于:
- 大文件下载
- 媒体资源获取
- 需要展示用户等待反馈的场景
如果只是很小的接口响应,通常不会单独做下载进度 UI。
XHR 里的下载进度
在 XHR 里,可以直接监听请求本身的 progress 事件:
js
const xhr = new XMLHttpRequest()
xhr.open('GET', '/download-endpoint')
xhr.responseType = 'blob'
xhr.addEventListener('progress', (event) => {
if (!event.lengthComputable) return
const percent = (event.loaded / event.total) * 100
console.log(percent.toFixed(2) + '%')
})这里通常看的也是:
loadedtotallengthComputable
一个基础例子
js
const xhr = new XMLHttpRequest()
xhr.open('GET', '/download-endpoint')
xhr.responseType = 'blob'
xhr.addEventListener('progress', (event) => {
if (!event.lengthComputable) return
const percent = (event.loaded / event.total) * 100
console.log(percent.toFixed(2) + '%')
})
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
const blob = xhr.response
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = 'filename.ext'
a.click()
URL.revokeObjectURL(url)
}
}
xhr.send()fetch 为什么下载进度更绕一点
因为 fetch 常见的 response.blob()、response.json() 这类用法,本身不会自动把下载进度暴露成一个现成回调。
如果需要下载进度,通常要自己读取响应流:
js
const response = await fetch('/download-endpoint')
const total = Number(response.headers.get('Content-Length')) || 0
const reader = response.body.getReader()
let loaded = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
loaded += value.length
if (total > 0) {
const percent = (loaded / total) * 100
console.log(percent.toFixed(2) + '%')
}
}所以在“必须要稳定下载进度”的场景里,XHR 往往更直接。
Axios 为什么也能做
Axios 浏览器端底层通常仍然依赖 XHR,所以可以用:
js
axios.get('/download-endpoint', {
responseType: 'blob',
onDownloadProgress(progressEvent) {
const percent = (progressEvent.loaded / progressEvent.total) * 100
console.log(percent.toFixed(2) + '%')
},
})一个容易忽略的边界
下载进度能不能算百分比,依赖服务端是否提供总大小信息。
如果拿不到 Content-Length,常见结果就是:
- 能知道已经收了多少字节
- 但不能稳定计算百分比
这时更适合展示:
- 已下载大小
- 加载中状态
而不是一个精确进度条。
使用建议
- 需要稳定下载进度时,优先考虑 XHR 或基于 XHR 的方案。
- 使用
fetch做下载进度时,要准备自己处理响应流。 - 如果没有总大小信息,不要强行展示百分比。
- 下载完成后若用
blob生成临时地址,记得URL.revokeObjectURL()。
