AJAX进度监控
一、AJAX进度监控
1、Ajax 的两种原生实现:XHR vs Fetch
Ajax(异步请求)的两种核心原生 API,是实现进度监控的基础,二者在功能、更新状态和进度监控支持上差异显著。
| 对比维度 | XMLHttpRequest(XHR) | Fetch API |
|---|---|---|
| 状态 | 已停止更新(浏览器仍兼容,但无新功能) | 持续更新(W3C 标准,新增流式处理等功能) |
| 功能覆盖 | 基础异步请求能力,支持进度监控(上传 + 响应) | 覆盖 XHR 核心功能,支持 Promise、拦截器等,但不支持上传进度监控 |
| 第三方库依赖 | axios、jQuery.ajax(内部基于 XHR 封装) | umijs request、fetch-interceptor(基于 Fetch 封装) |
| 面试高频度 | 极高(常问与 Fetch 的区别、进度监控实现) | 极高 |
2、响应进度监控实现(XHR + Fetch)
响应进度监控:指“服务器向客户端传输数据时”,实时计算已传输数据占总数据的比例(如加载大图片、Excel 文件时的进度条)。
2.1 XHR 实现响应进度监控(简单直观)
XHR 通过监听 progress 事件实现,事件对象会返回 loaded(已传输字节数)和 total(总字节数),直接用于计算进度。
代码示例:
js
// 1. 创建XHR实例
const xhr = new XMLHttpRequest();
// 2. 配置请求(GET请求,目标为大文件资源)
xhr.open('GET', '/api/download/large-file.xlsx', true);
// 3. 监听progress事件(核心:实时获取进度)
xhr.addEventListener('progress', (e) => {
// 关键判断:确保total有值(部分响应可能不返回Content-Length)
if (e.lengthComputable) {
// 计算进度百分比(保留2位小数)
const progressPercent = ((e.loaded / e.total) * 100).toFixed(2);
// 更新UI(如进度条宽度、百分比文字)
document.getElementById('progress-bar').style.width = `${progressPercent}%`;
document.getElementById('progress-text').textContent = `${progressPercent}%`;
}
});
// 4. 监听请求成功/失败
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
console.log('文件下载成功');
// 处理响应数据(如解析Excel、生成下载链接)
} else {
console.error('请求失败,状态码:', xhr.status);
}
});
xhr.addEventListener('error', () => {
console.error('网络错误,请求中断');
});
// 5. 发送请求
xhr.send();2.2 Fetch 实现响应进度监控(稍复杂,需手动处理)
Fetch 本身不直接暴露 progress 事件,需通过 HTTP 响应头的 Content-Length 字段获取总数据量,再通过响应体的 getReader() 方法逐块读取数据,累加已读取字节数计算进度。
核心逻辑
- 从响应头
Content-Length中解析总数据量(注意:需服务器正确返回该字段); - 通过
response.body.getReader()获取 “读取器”,逐块读取响应数据; - 每次读取后累加已读取字节数,与总数据量对比计算进度。
代码示例:
js
// 1. 发送Fetch请求
fetch('/api/download/large-file.xlsx')
.then(async (response) => {
// 2. 从响应头获取总数据量(需转为数字类型)
const totalBytes = Number(response.headers.get('Content-Length'));
if (!totalBytes) {
throw new Error('服务器未返回Content-Length,无法计算进度');
}
// 3. 获取响应体读取器(stream流读取)
const reader = response.body.getReader();
// 4. 初始化已读取字节数
let loadedBytes = 0;
// 5. 循环读取数据块(直到读取完成)
while (true) {
// 读取一块数据(done为true表示读取完成)
const { done, value } = await reader.read();
if (done) break; // 读取完成,退出循环
// 累加已读取字节数(value是Uint8Array,length即字节数)
loadedBytes += value.length;
// 6. 计算进度并更新UI
const progressPercent = ((loadedBytes / totalBytes) * 100).toFixed(2);
document.getElementById('progress-bar').style.width = `${progressPercent}%`;
document.getElementById('progress-text').textContent = `${progressPercent}%`;
}
// 7. 读取完成,处理最终数据(如转为Blob用于下载)
const blob = new Blob([await response.blob()], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
const downloadUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = downloadUrl;
a.download = 'large-file.xlsx';
a.click();
URL.revokeObjectURL(downloadUrl); // 释放URL资源
})
.catch((error) => {
console.error('请求失败:', error);
});3、上传进度监控实现(仅 XHR 支持)
上传进度监控: 指 “客户端向服务器传输数据时” 的进度(如文件上传、大表单提交),Fetch 目前不支持该功能,仅 XHR 可实现。
XHR 实现上传进度监控(核心:upload 属性)
XHR 的 upload 对象专门用于监听上传相关事件,通过 upload.onprogress 即可获取上传进度。
html
<!-- 页面结构:文件选择框 + 进度条 -->
<input type="file" id="file-input" accept=".jpg,.png">
<div class="progress-bar" id="upload-progress-bar" style="width: 0%; height: 8px; background: #42b983;"></div>
<div id="upload-progress-text">0%</div>js
// 1. 获取DOM元素
const fileInput = document.getElementById('file-input');
const progressBar = document.getElementById('upload-progress-bar');
const progressText = document.getElementById('upload-progress-text');
// 2. 监听文件选择事件
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0]; // 获取选中的文件
if (!file) return;
// 3. 创建FormData(用于传输文件)
const formData = new FormData();
formData.append('uploadFile', file); // 'uploadFile'为后端接收的参数名
// 4. 创建XHR实例并配置
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload/file', true);
// 5. 监听上传进度(核心:upload.onprogress)
xhr.upload.addEventListener('progress', (e) => {
if (e.lengthComputable) {
const progressPercent = ((e.loaded / e.total) * 100).toFixed(2);
progressBar.style.width = `${progressPercent}%`;
progressText.textContent = `${progressPercent}%`;
}
});
// 6. 监听上传成功/失败
xhr.addEventListener('load', () => {
if (xhr.status >= 200 && xhr.status < 300) {
const result = JSON.parse(xhr.responseText);
console.log('上传成功,文件ID:', result.fileId);
progressText.textContent = '上传完成!';
} else {
console.error('上传失败,状态码:', xhr.status);
progressText.textContent = '上传失败';
}
});
xhr.addEventListener('error', () => {
console.error('网络错误,上传中断');
progressText.textContent = '上传中断';
});
// 7. 发送请求(携带FormData)
xhr.send(formData);
});关键注意事项
- Content-Length 字段的必要性:无论是 XHR 还是 Fetch,计算进度都依赖服务器返回的 Content-Length 响应头(表示总数据量)。若服务器未返回该字段,e.lengthComputable 会为 false,无法计算真实进度。
- Fetch 的局限性:截至 2024 年 5 月,Fetch API 仍不支持上传进度监控,若业务需上传进度(如文件上传),需优先使用 XHR 或基于 XHR 封装的库(如 axios)。
- 兼容性:XHR 兼容所有现代浏览器及 IE(IE7+);Fetch 在 IE 中完全不支持,若需兼容 IE,需使用 whatwg-fetch 等 polyfill。
