Lazy loaded image
前端
前端必备:详解 XHR 与 Fetch 获取请求进度 (下载/上传进度条,面试常问)
字数 2251阅读时长 6 分钟
2024-6-28
2025-4-30
type
status
date
slug
summary
tags
category
icon
password
Meta Description: 学习如何使用 JavaScript 中的 XMLHttpRequest (XHR) 和 Fetch API 获取并展示 HTTP 请求的下载(响应)和上传进度。详解 onprogress 事件和 ReadableStream,提升大文件传输用户体验,解决常见面试题。
(文章正文)

引言:提升用户体验的“请求进度条”

在前端开发中,尤其是在处理大文件下载或需要较长时间才能完成的 API 请求时,向用户实时展示请求的响应进度(或上传进度)是一个常见的需求。如下图所示,一个从 0% 到 100% 的进度条可以极大地提升用户体验,缓解用户等待的焦虑。
实现这个动画效果本身不难,像 Ant Design 等 UI 库都提供了现成的 Progress 组件。
真正的难点在于:如何实时获取到 HTTP 请求的进度数据?
这个问题近年来也成为了前端面试中的高频考点,面试官通常会分别考察你对传统 XMLHttpRequest (XHR) 和现代 Fetch API 获取进度方式的理解。

一、 获取下载(响应)进度

这里我们主要关注如何获取服务器响应数据的下载进度。

1. 使用 XMLHttpRequest (XHR)

对于 XHR,获取响应进度相对直接。XMLHttpRequest 实例提供了一个 progress 事件,专门用于监听数据传输的进度。
 
这个 progress 事件的回调函数会接收一个 ProgressEvent 对象,其中包含两个关键属性:
  • event.loaded: 到目前为止已接收到的字节数(已加载的数据长度)。
  • event.total: 响应数据的总字节数(数据总长度)。这个值需要服务器在响应头中提供 Content-Length 才能获取到。
实现思路: 通过 (event.loaded / event.total) * 100 就可以计算出当前的下载百分比。
代码示例 (结合 UI 展示):
假设我们有一个简单的 Express 后端服务用于测试:
前端页面结构 (使用 Ant Design 的 Button 和 Progress):
核心 JavaScript 代码,监听 progress 事件并更新进度条:
控制台输出的百分比变化:
将计算出的百分比 percentComplete 传递给 Progress 组件,即可实现动态进度条效果:
最终效果:

2. 使用 Fetch API

Fetch API 是现代的、基于 Promise 的网络请求接口,但它没有像 XHR 那样直接提供 onprogress 事件来监听响应进度。我们需要换一种思路,利用 Fetch 返回的 Response 对象的 body (一个 ReadableStream) 来手动读取和计算进度。
实现思路:
  1. 获取总长度 (total):Response 对象的 headers 中获取 Content-Length 响应头的值。注意:服务器必须发送此响应头,否则无法计算总进度。
  1. 读取已加载长度 (loaded): 使用 response.body.getReader() 获取一个读取器 (Reader),然后循环调用 reader.read() 方法。每次 read() 返回一个包含 { value: chunk, done: boolean } 的对象,其中 chunk 是一个 Uint8Array (数据块)。累加每次读取到的 chunk.length 即可得到当前已加载的字节数。
代码示例:
代码逻辑解释 (对应图片): 获取 Content-Length:
使用 body.getReader 读取流并累加 loaded:
最终效果 (与 XHR 类似):
Fetch 的关键点:
  • 依赖服务器正确设置 Content-Length 响应头。
  • 实现逻辑比 XHR 复杂,需要手动处理流式读取。
  • 提供了更底层的控制能力。

二、 获取上传(请求)进度

了解了如何获取下载进度,那么上传文件时的进度又该如何监听呢?

使用 XMLHttpRequest (XHR)

XHR 对上传进度也提供了原生支持。它通过 XMLHttpRequest.upload 对象上的 progress 事件来实现。
 
实现思路: 监听 xhr.upload.onprogress 事件,同样使用 event.loaded (已上传字节数) 和 event.total (总上传字节数) 来计算百分比。
代码示例:
代码逻辑解释 (对应图片):

使用 Fetch API (上传进度)

需要注意的是,Fetch API 原生并不直接支持监听上传请求体本身的进度。虽然 Request 对象可以接受 ReadableStream 作为 body,但浏览器目前没有提供标准的方式来监测这个流被发送出去的进度。
获取 Fetch 上传进度通常需要更复杂的策略,例如:
  1. 结合 Service Worker: Service Worker 可以拦截请求,理论上可以读取请求体流并计算进度,但这实现复杂且有性能考量。
  1. 分块上传 + 服务端反馈: 将大文件切分成小块,逐个上传。每上传完一块,服务端可以返回进度信息,或者客户端根据已上传块数计算大致进度。
  1. WebSocket 或 Server-Sent Events: 上传过程中,客户端通过 WebSocket 或 SSE 与服务端保持连接,服务端实时推送上传进度信息给客户端。
总结: 对于需要精确上传进度的场景,目前 XHR 仍然是更简单、直接的选择

结语

掌握如何在 JavaScript 中获取 HTTP 请求的下载和上传进度,对于优化大文件传输、提升用户体验至关重要,同时也是前端面试中考察网络知识的一个常见切入点。
  • 对于下载(响应)进度
    • XHR 使用 onprogress 事件,简单直接。
    • Fetch 需要利用 response.bodyReadableStreamContent-Length 响应头,手动计算。
  • 对于上传(请求)进度
    • XHR 使用 xhr.upload.onprogress 事件,原生支持。
    • Fetch 原生不支持,需要采用更复杂的策略。
希望本文能帮助你理解这两种常用网络请求方式在进度获取上的差异和实现方法!
上一篇
纯 CSS 实现酷炫卷轴滚动动画 (联动页面滚动,含 JS 兼容方案)
下一篇
8 款优质 Vue 3 + Naive UI Admin 开箱即用模板 (Vite + TS) - 提升开发效率,告别重复搭建