Skip to content

下载进度

下载进度和上传进度很像,核心都是“前端能不能知道已经传输了多少数据”。

什么时候需要下载进度

更常见于:

  • 大文件下载
  • 媒体资源获取
  • 需要展示用户等待反馈的场景

如果只是很小的接口响应,通常不会单独做下载进度 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) + '%')
})

这里通常看的也是:

  • loaded
  • total
  • lengthComputable

一个基础例子

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()

基于 MIT 许可发布