1. 前言
在日常 web 开发中,ajax(asynchronous javascript and xml)
被广泛用于异步请求数据,而无需刷新整个页面。然而,当涉及到上传下载文件或执行长时间运行的任务时,为了提升用户体验通常我们需要显示执行的进度条,那么监控请求的进度就变得尤为重要。
这里博主给大家讲解 xmlhttprequest
和 fetch api
以及 axios封装
在进度监控上不同的实现方式
进度监控的核心场景 :
1. 大文件上传/下载
2. 实时数据传输(如视频流)
3. 长耗时api请求
4. 用户交互反馈优化
2. 基于xmlhttprequest的进度监控
在 javascript
中,xmlhttprequest
提供了 progress
事件,允许我们监听请求的进度。
2.1 基础版文件上传监控
<input type="file" id="fileinput"> <progress id="uploadprogress" value="0" max="100"></progress> <script> document.getelementbyid('fileinput').addeventlistener('change', function(e) { const file = e.target.files[0]; if (!file) return; const xhr = new xmlhttprequest(); const progressbar = document.getelementbyid('uploadprogress'); xhr.upload.addeventlistener('progress', (e) => { if (e.lengthcomputable) { const percent = (e.loaded / e.total) * 100; progressbar.value = percent; console.log(`上传进度: ${percent.tofixed(1)}%`); } }); xhr.addeventlistener('load', () => { console.log('上传完成'); }); xhr.open('post', '/upload', true); xhr.send(file); }); </script>
2.2 增强版多事件监控
我们还可以集合 progress
进行多事件监控,如下代码:
function uploadwithprogress(file) { return new promise((resolve, reject) => { const xhr = new xmlhttprequest(); // 上传进度监控 xhr.upload.addeventlistener('progress', (e) => { handleprogress('upload', e); }); // 下载进度监控(当服务器返回大数据时) xhr.addeventlistener('progress', (e) => { handleprogress('download', e); }); xhr.addeventlistener('error', reject); xhr.addeventlistener('abort', reject); xhr.addeventlistener('load', () => { if (xhr.status >= 200 && xhr.status < 300) { resolve(xhr.response); } else { reject(xhr.statustext); } }); xhr.open('post', '/upload'); xhr.setrequestheader('content-type', 'application/octet-stream'); xhr.send(file); function handleprogress(type, e) { if (e.lengthcomputable) { const percent = (e.loaded / e.total) * 100; console.log(`${type} progress: ${percent.tofixed(1)}%`); } } }); }
3. 基于fetch api 的进度监控
3.1 fetch api + readablestream实现下载监控
fetch api
本身没有直接提供进度事件,但我们可以利用 readablestream
对响应体进行分段读取,从而计算已加载的字节数。
当第一次请求链接 await fetch(url)
的时候通过获取 headers 中 content-length 返回的请求资源总大小,再结合 response.body.getreader()
来读取 body 内容来实现!
具体参考代码如下,小伙伴可以根据自身需求进行调整:
async function downloadlargefile(url) { const response = await fetch(url); const reader = response.body.getreader(); const contentlength = +response.headers.get('content-length'); let receivedlength = 0; const chunks = []; while(true) { const {done, value} = await reader.read(); if (done) break; chunks.push(value); receivedlength += value.length; const percent = (receivedlength / contentlength) * 100; console.log(`下载进度: ${percent.tofixed(1)}%`); } const blob = new blob(chunks); return blob; } // 使用示例 downloadlargefile('/large-file.zip') .then(blob => { const url = url.createobjecturl(blob); const a = document.createelement('a'); a.href = url; a.download = 'file.zip'; a.click(); });
3.2 fetch api 上传进度监控(伪方案)
fetch api
原生并不支持上传进度监控。不过可以采用将文件包装成一个自定义的 readablestream 来实现 “伪”上传进度监控 。
需要注意的是,由于各浏览器对 request 流处理的支持程度不一,该方法可能并非在所有环境下都能稳定工作。博主建议非必要不要采用这种方式 ~
<!doctype html> <html lang="zh"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>fetch api 上传进度监控</title> </head> <body> <input type="file" id="fileinput"> <button onclick="uploadfile()">上传文件</button> <progress id="progressbar" value="0" max="100"></progress> <span id="progresstext">0%</span> <script> function uploadfile() { const fileinput = document.getelementbyid('fileinput'); const file = fileinput.files[0]; if (!file) { alert('请选择文件'); return; } const progressbar = document.getelementbyid('progressbar'); const progresstext = document.getelementbyid('progresstext'); const total = file.size; let uploaded = 0; // 构造一个自定义的 readablestream 来包装文件流 const stream = new readablestream({ start(controller) { const reader = file.stream().getreader(); function push() { reader.read().then(({ done, value }) => { if (done) { controller.close(); return; } uploaded += value.bytelength; const percent = (uploaded / total) * 100; progressbar.value = percent; progresstext.innertext = percent.tofixed(2) + '%'; controller.enqueue(value); push(); }).catch(error => { console.error('读取文件错误:', error); controller.error(error); }); } push(); } }); // 使用 fetch api 发送 post 请求 fetch('/upload', { method: 'post', headers: { // 根据后端要求设置合适的 content-type // 注意:如果使用 formdata 上传文件,浏览器会自动设置 multipart/form-data 'content-type': 'application/octet-stream' }, body: stream }) .then(response => response.json()) .then(data => { console.log('上传成功:', data); alert('上传完成'); }) .catch(error => { console.error('上传失败:', error); alert('上传失败'); }); } </script> </body> </html>
注意事项
- 上传进度:fetch api 本身不提供上传进度事件,上述方法通过包装文件流来模拟上传进度,但并非所有浏览器都支持这种方式,稳定性可能不如 xmlhttprequest
- 内容类型:如果后端要求 multipart/form-data 格式,建议仍采用 xmlhttprequest 或使用 formdata 对象,因为自定义流方式上传数据可能需要后端特殊处理
4. axios封装进度监控方案
通过封装 axios 请求,可以同时监听上传和下载的进度,提升用户体验,再次之前我们先来看看未封装前最原始的上传和下载是如何实现的~
4.1 axios 上传进度监控
axios
支持通过配置项 onuploadprogress
来监听上传进度。以下示例展示了如何使用 axios
上传文件,并在页面上显示进度信息:
<!doctype html> <html lang="zh"> <head> <meta charset="utf-8"> <title>axios 上传进度监控</title> </head> <body> <h2>文件上传(axios)</h2> <input type="file" id="fileinput"> <button onclick="uploadfile()">上传文件</button> <br> <progress id="uploadprogress" value="0" max="100" style="width: 300px;"></progress> <span id="uploadtext">0%</span> <!-- 引入 axios 库 --> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> function uploadfile() { const fileinput = document.getelementbyid('fileinput'); const file = fileinput.files[0]; if (!file) { alert('请选择文件'); return; } const formdata = new formdata(); formdata.append('file', file); axios.post('/upload', formdata, { headers: { 'content-type': 'multipart/form-data' }, onuploadprogress: function (progressevent) { if (progressevent.lengthcomputable) { const percent = math.round((progressevent.loaded * 100) / progressevent.total); document.getelementbyid('uploadprogress').value = percent; document.getelementbyid('uploadtext').innertext = percent + '%'; } } }) .then(response => { alert('上传成功'); console.log(response.data); }) .catch(error => { alert('上传失败'); console.error(error); }); } </script> </body> </html>
代码解释:
使用 formdata 对象包装上传的文件;
通过 axios 的 onuploadprogress 事件监听上传过程,并实时更新进度条和百分比显示。
4.2 axios 下载进度监控
同理,axios
还支持 ondownloadprogress
事件来监控文件下载进度。下面的示例展示了如何通过 axios
下载文件并实时显示下载进度:
<!doctype html> <html lang="zh"> <head> <meta charset="utf-8"> <title>axios 下载进度监控</title> </head> <body> <h2>文件下载(axios)</h2> <button onclick="downloadfile()">下载文件</button> <br> <progress id="downloadprogress" value="0" max="100" style="width: 300px;"></progress> <span id="downloadtext">0%</span> <!-- 引入 axios 库 --> <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script> <script> function downloadfile() { axios.get('/download', { responsetype: 'blob', // 指定响应数据类型为 blob ondownloadprogress: function (progressevent) { if (progressevent.lengthcomputable) { const percent = math.round((progressevent.loaded * 100) / progressevent.total); document.getelementbyid('downloadprogress').value = percent; document.getelementbyid('downloadtext').innertext = percent + '%'; } } }) .then(response => { // 创建一个临时链接下载文件 const url = window.url.createobjecturl(new blob([response.data])); const a = document.createelement('a'); a.href = url; a.download = 'downloaded_file'; document.body.appendchild(a); a.click(); a.remove(); window.url.revokeobjecturl(url); }) .catch(error => { alert('下载失败'); console.error(error); }); } </script> </body> </html>
代码解释:
1、我们通过 axios.get 请求下载文件,并设置 responsetype 为 blob;
2、通过 ondownloadprogress 事件监听下载进度,并更新进度条;
3、下载完成后利用 blob 和临时链接触发浏览器下载文件。
4.3 封装 axios 实例
为了在项目中更方便地使用进度监控功能,可以将 axios
进行封装。例如,我们可以创建一个 axios 实例,并在请求配置中统一处理上传和下载进度
import axios from 'axios'; // 请求配置 const axiosinstance = axios.create({ onuploadprogress: progressevent => { const percent = math.round( (progressevent.loaded * 100) / progressevent.total ); console.log(`上传进度: ${percent}%`); }, ondownloadprogress: progressevent => { const percent = math.round( (progressevent.loaded * 100) / progressevent.total ); console.log(`下载进度: ${percent}%`); } }); // 封装上传方法 async function axiosupload(file) { const formdata = new formdata(); formdata.append('file', file); try { const response = await axiosinstance.post('/upload', formdata, { headers: {'content-type': 'multipart/form-data'} }); return response.data; } catch (error) { console.error('上传失败:', error); throw error; } }
使用时,在页面中调用封装方法即可,这样通过封装后的 axios 实例,我们可以在项目中更加方便地复用进度监控功能。
5. 特殊场景处理技巧
通常在一些特殊场景下,针对进度监控我们还需要一些处理技巧,这里博主分享两个分别是 分块上传监控
和 带宽计算与预估
5. 1 分块上传监控
async function chunkedupload(file, chunksize = 1024 * 1024) { const chunks = math.ceil(file.size / chunksize); let uploadedchunks = 0; for (let i = 0; i < chunks; i++) { const start = i * chunksize; const end = math.min(start + chunksize, file.size); const chunk = file.slice(start, end); await axios.post('/upload-chunk', chunk, { headers: {'content-range': `bytes ${start}-${end-1}/${file.size}`}, onuploadprogress: e => { const chunkpercent = (e.loaded / e.total) * 100; const totalpercent = ((uploadedchunks + e.loaded) / file.size) * 100; console.log(`分块进度: ${chunkpercent.tofixed(1)}%`); console.log(`总进度: ${totalpercent.tofixed(1)}%`); } }); uploadedchunks += chunk.size; } }
5. 2 带宽计算与预估
let starttime; let lastloaded = 0; xhr.upload.addeventlistener('progress', e => { if (!starttime) starttime = date.now(); const currenttime = date.now(); const elapsed = (currenttime - starttime) / 1000; // 秒 const speed = e.loaded / elapsed; // bytes/s const remaining = (e.total - e.loaded) / speed; // 剩余时间 console.log(`传输速度: ${formatbytes(speed)}/s`); console.log(`预计剩余时间: ${remaining.tofixed(1)}秒`); }); function formatbytes(bytes) { const units = ['b', 'kb', 'mb', 'gb']; let unitindex = 0; while (bytes >= 1024 && unitindex < units.length - 1) { bytes /= 1024; unitindex++; } return `${bytes.tofixed(1)} ${units[unitindex]}`; }
6. 结语
本篇文章介绍了如何在前端请求中监控上传和下载进度,并提供了完整的前端和后端代码示例。通过合理运用这些技术,开发者可以构建出具有专业级进度反馈的web应用,显著提升用户在处理大文件传输时的体验。
希望能帮助小伙伴们在项目中更好地处理大文件传输,提高用户体验!
到此这篇关于ajax请求上传下载进度监控指南的文章就介绍到这了,更多相关ajax上传下载内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论