[前端开发Vue]在Axios中,怎么实现实时监听上传数据进度、监听接口返回数据进度、如何终止网络请求。实现上传文件实时进度条以及下载文件实时进度条。
'# [前端开发Vue]在Axios中,怎么实现实时监听上传数据进度、监听接口返回数据进度、如何终止网络请求。实现上传文件实时进度条以及下载文件实时进度条
一、背景与问题
在现代Web开发中,文件上传/下载的进度可视化是提升用户体验的关键要素。传统Axios的axios.get/axios.post方法无法直接获取请求的实时进度信息,这在处理大文件传输时尤为明显。开发者常遇到以下问题:
- 无法实时监控文件上传/下载的进度百分比
- 无法在请求中途主动终止正在进行的网络请求
- 无法在前端实现精确的进度条渲染
- 无法处理请求中断后的资源清理
这些问题在文件上传(如用户头像、文档上传)和数据下载(如大体积API响应)场景中尤为突出。本文将深入探讨Axios的底层实现机制,结合Vue组件架构,提供完整的解决方案。
二、基本原理
Axios的请求流程包含三个关键阶段:
- 请求准备阶段:创建HTTP请求对象,配置请求头、超时等参数
- 传输阶段:网络请求的发送与数据传输
- 响应阶段:接收服务器响应并处理数据
Axios通过onUploadProgress和onDownloadProgress两个回调函数,分别监控请求体的上传进度和响应体的下载进度。这两个回调函数基于浏览器的XMLHttpRequest对象实现,其本质是通过监听progress事件来获取进度信息。
三、环境准备
确保项目中已安装Axios和Vue的依赖:
npm install axios vue在Vue组件中需要使用Axios的拦截器来统一处理进度监控,同时需要处理跨域问题(如果存在)。
四、核心实现
1. 上传文件进度条实现
<template>
<div>
<input type="file" @change="handleFileChange" />
<div v-if="progress > 0">
上传进度:{{ progress }}%
<progress :value="progress" max="100"></progress>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
file: null,
progress: 0
};
},
methods: {
handleFileChange(event) {
this.file = event.target.files[0];
},
async uploadFile() {
const formData = new FormData();
formData.append('file', this.file);
const config = {
onUploadProgress: (progressEvent) => {
// 计算上传百分比
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
this.progress = percent;
}
};
try {
const response = await axios.post('/upload', formData, config);
console.log('上传成功:', response.data);
} catch (error) {
console.error('上传失败:', error);
}
}
}
};
</script>关键代码解析:
FormData对象用于处理文件上传onUploadProgress回调接收progressEvent对象,包含loaded(已传输字节数)和total(总字节数)- 进度计算使用
Math.round()确保百分比整数化 progress数据绑定到模板中的进度条组件
2. 下载文件进度条实现
<template>
<div>
<button @click="downloadFile">下载文件</button>
<div v-if="progress > 0">
下载进度:{{ progress }}%
<progress :value="progress" max="100"></progress>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
progress: 0
};
},
methods: {
async downloadFile() {
const config = {
onDownloadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
this.progress = percent;
}
};
try {
const response = await axios.get('/download', config);
console.log('下载成功:', response.data);
} catch (error) {
console.error('下载失败:', error);
}
}
}
};
</script>关键代码解析:
onDownloadProgress回调用于监控响应体的下载进度- 同样使用
loaded/total计算百分比 - 注意下载文件时需要处理
Content-Length头信息,若服务器未设置该头,需通过response.headers['content-length']获取
3. 请求终止机制实现
<template>
<div>
<button @click="uploadFile">上传文件</button>
<button @click="cancelUpload">取消上传</button>
<div v-if="progress > 0">
上传进度:{{ progress }}%
<progress :value="progress" max="100"></progress>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
file: null,
progress: 0,
cancelToken: null
};
},
methods: {
handleFileChange(event) {
this.file = event.target.files[0];
},
async uploadFile() {
const formData = new FormData();
formData.append('file', this.file);
// 创建取消令牌
this.cancelToken = axios.CancelToken.source();
const config = {
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
this.progress = percent;
},
cancelToken: this.cancelToken.token
};
try {
const response = await axios.post('/upload', formData, config);
console.log('上传成功:', response.data);
} catch (error) {
if (axios.isCancel(error)) {
console.log('上传已取消');
} else {
console.error('上传失败:', error);
}
}
},
cancelUpload() {
if (this.cancelToken) {
this.cancelToken.cancel('用户主动取消上传');
}
}
}
};
</script>关键代码解析:
- 使用
axios.CancelToken创建取消令牌 - 在请求配置中注入
cancelToken对象 - 通过
cancelToken.cancel()方法主动终止请求 - 使用
axios.isCancel()判断是否为取消请求
五、完整案例
文件上传下载进度条完整案例
<template>
<div>
<h2>文件上传</h2>
<input type="file" @change="handleFileChange" />
<button @click="uploadFile">上传文件</button>
<button @click="cancelUpload">取消上传</button>
<div v-if="uploadProgress > 0">
上传进度:{{ uploadProgress }}%
<progress :value="uploadProgress" max="100"></progress>
</div>
<h2>文件下载</h2>
<button @click="downloadFile">下载文件</button>
<div v-if="downloadProgress > 0">
下载进度:{{ downloadProgress }}%
<progress :value="downloadProgress" max="100"></progress>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
uploadFile: null,
uploadProgress: 0,
downloadProgress: 0,
uploadCancelToken: null,
downloadCancelToken: null
};
},
methods: {
handleFileChange(event) {
this.uploadFile = event.target.files[0];
},
async uploadFile() {
const formData = new FormData();
formData.append('file', this.uploadFile);
this.uploadCancelToken = axios.CancelToken.source();
const config = {
onUploadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
this.uploadProgress = percent;
},
cancelToken: this.uploadCancelToken.token
};
try {
const response = await axios.post('/upload', formData, config);
console.log('上传成功:', response.data);
} catch (error) {
if (axios.isCancel(error)) {
console.log('上传已取消');
} else {
console.error('上传失败:', error);
}
}
},
cancelUpload() {
if (this.uploadCancelToken) {
this.uploadCancelToken.cancel('用户主动取消上传');
}
},
async downloadFile() {
const config = {
onDownloadProgress: (progressEvent) => {
const percent = Math.round((progressEvent.loaded / progressEvent.total) * 100);
this.downloadProgress = percent;
},
cancelToken: this.downloadCancelToken ? this.downloadCancelToken.token : axios.CancelToken.source().token
};
try {
const response = await axios.get('/download', config);
console.log('下载成功:', response.data);
} catch (error) {
if (axios.isCancel(error)) {
console.log('下载已取消');
} else {
console.error('下载失败:', error);
}
}
},
cancelDownload() {
if (this.downloadCancelToken) {
this.downloadCancelToken.cancel('用户主动取消下载');
}
}
}
};
</script>关键代码解析:
- 同时处理上传和下载的进度条
- 为上传和下载分别创建独立的取消令牌
- 在下载时处理可能的
Content-Length缺失情况 - 通过
axios.isCancel()判断请求是否被取消
六、源码解析
Axios的onUploadProgress和onDownloadProgress的实现原理如下:
// axios/lib/core/createAxios.js
function createAxios(config) {
const httpAdapter = config.httpAdapter || getAdapter(config);
return function axios(config) {
const instanceConfig = mergeConfig(config, this.defaults);
const { headers = {}, timeout, ...rest } = instanceConfig;
const request = (url, config) => {
const { headers, timeout, ...rest } = mergeConfig(config, instanceConfig);
const xhr = new XMLHttpRequest();
// 上传进度监听
xhr.upload.onprogress = (event) => {
if (event.lengthComputable) {
config.onUploadProgress({
loaded: event.loaded,
total: event.total
});
}
};
// 下载进度监听
xhr.onprogress = (event) => {
if (event.lengthComputable) {
config.onDownloadProgress({
loaded: event.loaded,
total: event.total
});
}
};
// ...其他配置
};
};
}关键点说明:
- 使用
XMLHttpRequest的upload对象监听上传进度 - 使用
XMLHttpRequest的onprogress事件监听下载进度 lengthComputable属性用于判断是否可以获取总大小- 进度计算基于
event.loaded和event.total的比值
七、进阶使用
1. 使用AbortController替代CancelToken
const controller = new AbortController();
axios.post('/upload', data, {
signal: controller.signal
});
// 中止请求
controller.abort();优势:
- 更符合现代浏览器的API规范
- 支持
AbortSignal的onabort事件 - 与Fetch API兼容性更好
2. 结合Vue的响应式系统
import { ref, onMounted } from 'vue';
const progress = ref(0);
const isUploading = ref(false);
onMounted(() => {
const controller = new AbortController();
axios.post('/upload', data, {
signal: controller.signal,
onUploadProgress: (event) => {
progress.value = Math.round((event.loaded / event.total) * 100);
}
}).finally(() => {
isUploading.value = false;
});
});关键点:
- 使用Vue的
ref管理状态 - 在
onUploadProgress中更新响应式数据 - 使用
finally处理请求完成后的状态更新
3. 带缓存的进度监控
const cache = new Map();
function getCache(key) {
if (cache.has(key)) {
return cache.get(key);
}
return null;
}
function setCache(key, value) {
cache.set(key, value);
}应用场景:
- 多个并发请求的进度合并展示
- 避免重复计算相同的进度信息
- 提升性能的缓存策略
八、性能与工程实践
1. 性能优化策略
| 优化措施 | 说明 |
|---|---|
| 节流处理 | 使用setInterval代替频繁的进度更新 |
| 二进制处理 | 使用ArrayBuffer进行大数据处理 |
| 压缩传输 | 使用Gzip压缩减少传输体积 |
| 工具库使用 | 使用axios-progress-bar等第三方库 |
示例代码:
let progressInterval = null;
onUploadProgress(event) {
if (!progressInterval) {
progressInterval = setInterval(() => {
this.progress = Math.round((event.loaded / event.total) * 100);
}, 100);
}
}2. 异常处理机制
try {
await axios.post('/upload', data, {
onUploadProgress: (event) => {
this.progress = Math.round((event.loaded / event.total) * 100);
}
});
} catch (error) {
if (axios.isCancel(error)) {
console.log('请求已取消');
} else {
console.error('请求异常:', error.message);
}
}关键点:
- 区分取消请求和网络异常
- 处理
AbortError和NetworkError等不同类型的错误 - 清理可能残留的定时器或资源
3. 安全性考虑
- CSRF防护:在上传文件时需要校验CSRF Token
- 文件类型验证:在前端进行文件类型校验,防止恶意文件上传
- 大小限制:设置合理的文件大小限制,防止资源耗尽
- 敏感数据加密:对上传的敏感数据进行加密处理
九、常见问题与踩坑
1. 进度计算不准确
问题现象:
- 进度条显示0%或不更新
- 进度百分比计算错误
常见原因:
- 未设置
Content-Length头 - 使用了
multipart/form-data格式 - 服务器未正确发送
Content-Length头
解决方案:
- 使用
axios.get时通过response.headers['content-length']获取大小 - 对于
multipart/form-data,通过event.total获取总大小 - 在服务器端设置正确的
Content-Length头
2. 请求无法取消
问题现象:
- 调用
cancel()方法无效 - 请求仍然在后台运行
常见原因:
- 未正确创建和注入
CancelToken - 使用了
fetch而非axios进行请求 - 使用了不支持取消的HTTP客户端
解决方案:
- 确保使用
axios.CancelToken创建取消令牌 - 在请求配置中正确注入
cancelToken - 避免混用不同的HTTP客户端
3. 跨域问题
问题现象:
- 浏览器报错:
XMLHttpRequest cannot load ... No 'Access-Control-Allow-Origin' header is present on the requested resource
解决方案:
- 配置CORS策略
- 使用
axios的withCredentials选项 - 在后端设置
Access-Control-Allow-Origin头 - 使用代理服务器处理跨域请求
十、最佳实践
| 场景 | 推荐方案 |
|---|---|
| 文件上传 | 使用onUploadProgress配合FormData |
| 文件下载 | 使用onDownloadProgress配合Content-Length |
| 请求取消 | 使用CancelToken或AbortController |
| 大文件处理 | 使用分块传输和进度条展示 |
| 响应式更新 | 使用Vue的响应式系统更新状态 |
| 安全校验 | 在前端进行文件类型和大小校验 |
| 性能优化 | 使用节流处理和缓存策略 |
推荐实践:
- 对所有文件上传/下载请求都添加进度监控
- 为每个请求创建独立的取消令牌
- 使用
AbortController代替CancelToken(现代浏览器) - 对敏感数据进行加密处理
- 设置合理的文件大小限制
十一、总结
在Vue项目中使用Axios实现实时进度监控和请求终止,需要深入理解Axios的底层机制,特别是XMLHttpRequest的onprogress事件和CancelToken机制。通过合理使用onUploadProgress和onDownloadProgress回调,可以实现精确的上传/下载进度条,并通过取消令牌实现请求的主动终止。
在实际开发中,需要注意:
- 不同的HTTP方法和Content-Type对进度计算的影响
- 跨域请求的特殊处理
- 大文件处理时的性能优化
- 安全性校验和数据加密
- 响应式系统的状态更新
建议根据具体业务场景选择合适的实现方式,对于大文件传输和重要业务请求,推荐使用AbortController结合Fetch API实现更现代的解决方案。通过合理的设计和实践,可以显著提升用户体验和系统稳定性。
评论已关闭