通过监听XMLHttpRequest的progress事件或使用axios的onUploadProgress/onDownloadProgress,可以精确获取网络请求的实时进度,并渲染为可视化进度条。在实际开发中,无论上传大文件还是加载资源,进度条能有效缓解用户等待焦虑,同时提供可量化的反馈机制。
js网络请求进度条实现方法对比
为什么需要进度条
用户对无反馈的请求等待时间容忍度极低,据统计超过3秒就可能流失,进度条不仅告知“正在发生”,还给出剩余时间预期,直接提升交互完成度,在文件上传、数据导出、图片批量加载等场景下,进度条是刚需功能。
主流实现方案一览
目前前端实现进度条主要依赖三种底层方式,各有优劣。
- 原生XMLHttpRequest:直接支持progress事件,兼容性最好,但需要手动处理进度计算和UI更新。
- Axios:基于XHR封装,提供onUploadProgress和onDownloadProgress回调,使用简便,是大多数项目的首选。
- Fetch API:本身不提供进度事件,需借助ReadableStream或第三方库实现,控制更底层但复杂度高。
下表对比关键特性:
| 方案 | 进度支持 | 易用性 | 兼容性 |
|---|---|---|---|
| XMLHttpRequest | 原生支持 | 中等 | 最佳(所有浏览器) |
| Axios | 封装回调 | 高 | 较好(支持IE10+) |
| Fetch + ReadableStream | 需手动实现 | 低 | 仅现代浏览器 |
基于XMLHttpRequest的进度条实践
监听progress事件:上传与下载
XMLHttpRequest对象提供了两个progress事件来源:`xhr.upload` 用于监听上传进度,`xhr` 本身用于监听下载进度,事件对象包含 `loaded`(已传输字节)和 `total`(总字节数),通过 `event.lengthComputable` 可以判断 `total` 是否有效。
const xhr = new XMLHttpRequest();
xhr.open('POST', '/upload');
// 上传进度
xhr.upload.onprogress = (e) => {
if (e.lengthComputable) {
const percent = Math.round((e.loaded / e.total) 100);
// 更新进度条
}
};
// 下载进度(响应数据)
xhr.onprogress = (e) => {
// 同理
};
xhr.send(formData);
兼容性处理与注意事项
服务器必须返回 `Content-Length` 头部,否则 `total` 为0,无法计算百分比,对于压缩传输(如gzip),`total` 是压缩后的字节数,而 `loaded` 是解压后的接收量,会导致进度超过100%或不准,行业共识认为,在服务端关闭压缩或使用 `Transfer-Encoding: identity` 可获得更准确的进度,`xhr.upload.onprogress` 在部分浏览器中可能不触发,需做降级处理显示不确定进度动画。
代码封装与复用
你可以封装一个通用函数,传入回调,避免重复书写。
function requestWithProgress(url, options, onProgress) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', url);
xhr.upload.onprogress = onProgress; // 上传
xhr.onprogress = onProgress; // 下载
xhr.onload = () => resolve(xhr.response);
xhr.onerror = () => reject(xhr.statusText);
xhr.send(options.data);
});
}
使用Axios简化进度条开发
axios上传进度条怎么用?详细步骤
Axios 在请求配置中直接提供 `onUploadProgress` 和 `onDownloadProgress` 回调,无需手动处理事件绑定。
步骤1: 安装axios(如未引入)
步骤2: 发起请求,配置回调函数
import axios from 'axios';
const formData = new FormData();
formData.append('file', fileInput.files[0]);
axios.post('/upload', formData, {
onUploadProgress: (progressEvent) => {
const percent = Math.round(
(progressEvent.loaded 100) / progressEvent.total
);
// 更新进度条UI
console.log(`上传进度:${percent}%`);
},
onDownloadProgress: (progressEvent) => {
// 下载进度同理
}
}).then(response => {
// 处理结果
});
注意:onUploadProgress 仅在请求方法为 POST
、PUT、PATCH 等包含请求体时有效。onDownloadProgress 在获取响应数据时触发,适合下载文件或加载大JSON。
取消请求与错误处理
Axios 支持 `CancelToken` 或 `AbortController` 取消请求,取消后进度条应回退或重置。
const source = axios.CancelToken.source();
axios.post('/upload', data, {
cancelToken: source.token,
onUploadProgress: ...
});
source.cancel('操作取消'); // 触发catch
在 catch 中判断 axios.isCancel 来区分是取消还是网络错误,并更新进度条状态。
项目中的封装技巧
建议将进度回调抽取为公共方法,统一处理百分比计算和UI更新。
// utils/progress.js
export function updateProgress(loaded, total) {
if (!total) return '加载中...';
return `${Math.round((loaded / total) 100)}%`;
}
// 使用
onUploadProgress: (e) => {
const text = updateProgress(e.loaded, e.total);
progressBar.innerText = text;
}
Fetch API的进度监控替代方案
利用ReadableStream实现进度
Fetch 的 `response.body` 是一个 `ReadableStream`,可以读取数据块并累计大小,但需要知道总大小:服务器应在响应头中返回 `Content-Length`,由于 `ReadableStream` 读取的是解压后的数据,而 `Content-Length` 是压缩后的长度,两者可能不一致,导致进度不准,业内专家指出,在服务端禁用压缩或改用 `Content-Encoding: identity` 可以解决,但会牺牲传输效率。
const response = await fetch(url);
const contentLength = +response.headers.get('Content-Length');
const reader = response.body.getReader();
let receivedLength = 0;
while(true) {
const { done, value } = await reader.read();
if (done) break;
receivedLength += value.length;
const percent = contentLength ? Math.round((receivedLength / contentLength) 100) : 0;
// 更新进度
}
第三方库推荐
社区有 `progress-fetch` 等库封装了此类逻辑,但使用场景有限,多数情况下,对于需要进度监控的请求,直接使用 Axios 或 XHR 更省力。
性能与兼容性权衡
Fetch 方案不兼容 IE,且需要处理 `ReadableStream` 的锁定和取消,如果你的项目只支持现代浏览器,并且需要流式处理(如实时解析),Fetch 是合适选择,否则,XHR 或 Axios 更稳妥。
大文件上传进度条场景优化
分片上传与进度合并
当文件超过几百MB,通常会采用分片上传,每个分片独立的进度事件需要合并为整体进度:整体进度 = (已成功上传的分片大小之和) / 文件总大小,在每片上传完成后,累加该片大小,并触发UI更新。
let uploadedBytes = 0;
const totalBytes = file.size;
chunks.forEach((chunk, index) => {
uploadChunk(chunk).then(() => {
uploadedBytes += chunk.size;
const percent = Math.round((uploadedBytes / totalBytes) 100);
// 更新进度条
});
});
断点续传与进度恢复
将已上传的字节记录到 `localStorage` 或服务器,当页面刷新后重新读取,并跳过已上传部分,进度条初始化时根据已上传量计算初始百分比,避免重复上传。
js网络请求进度条常见问题
Q1: 为什么进度百分比突然跳到100%又回退?
A: 通常是服务器返回 `Content-Length` 与实际传输大小不符,或使用了压缩传输导致 `total` 错误,建议在服务端确认 `Content-Length` 正确,并关闭中间件(如代理)的压缩。
Q2: 上传进度条不更新是什么原因?
A: 检查 onUploadProgress 回调是否绑定正确,且请求体确实包含数据(如FormData),部分浏览器在上传小文件时可能不触发进度事件,因为文件太小瞬间完成,可考虑增加最小延迟动画。
Q3: 下载进度条能用于JSON数据吗?
A: 可以,但JSON通常很小,进度意义不大,对于大JSON(如大于10MB),下载进度能帮助用户感知完成时间,注意,如果服务器启用了gzip压缩,total 是压缩后大小,loaded 是解压后大小,会导致进度计算不准确,此时应使用 onDownloadProgress 并结合 Content-Length 原始值,或者直接显示“加载中”动画。
首发原创文章,作者:王坚,如若转载,请注明出处:https://idctop.com/article/538556.html



