AJAX請求上傳下載進度監(jiān)控實現(xiàn)方式
1. 前言
在日常 Web 開發(fā)中,AJAX(Asynchronous JavaScript and XML)
被廣泛用于異步請求數(shù)據(jù),而無需刷新整個頁面。然而,當(dāng)涉及到上傳下載文件或執(zhí)行長時間運行的任務(wù)時,為了提升用戶體驗通常我們需要顯示執(zhí)行的進度條,那么監(jiān)控請求的進度就變得尤為重要。
這里博主給大家講解 XMLHttpRequest
和 Fetch API
以及 Axios封裝
在進度監(jiān)控上不同的實現(xiàn)方式
進度監(jiān)控的核心場景 :
1. 大文件上傳/下載
2. 實時數(shù)據(jù)傳輸(如視頻流)
3. 長耗時API請求
4. 用戶交互反饋優(yōu)化
2. 基于XMLHttpRequest的進度監(jiān)控
在 JavaScript
中,XMLHttpRequest
提供了 progress
事件,允許我們監(jiān)聽請求的進度。
2.1 基礎(chǔ)版文件上傳監(jiān)控
<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 增強版多事件監(jiān)控
我們還可以集合 progress
進行多事件監(jiān)控,如下代碼:
function uploadWithProgress(file) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); // 上傳進度監(jiān)控 xhr.upload.addEventListener('progress', (e) => { handleProgress('upload', e); }); // 下載進度監(jiān)控(當(dāng)服務(wù)器返回大數(shù)據(jù)時) 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 的進度監(jiān)控
3.1 Fetch API + ReadableStream實現(xiàn)下載監(jiān)控
Fetch API
本身沒有直接提供進度事件,但我們可以利用 ReadableStream
對響應(yīng)體進行分段讀取,從而計算已加載的字節(jié)數(shù)。
當(dāng)?shù)谝淮握埱箧溄?await fetch(url)
的時候通過獲取 headers 中 Content-Length 返回的請求資源總大小,再結(jié)合 response.body.getReader()
來讀取 body 內(nèi)容來實現(xiàn)!
具體參考代碼如下,小伙伴可以根據(jù)自身需求進行調(diào)整:
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 上傳進度監(jiān)控(偽方案)
Fetch API
原生并不支持上傳進度監(jiān)控。不過可以采用將文件包裝成一個自定義的 ReadableStream 來實現(xiàn) “偽”上傳進度監(jiān)控 。
需要注意的是,由于各瀏覽器對 Request 流處理的支持程度不一,該方法可能并非在所有環(huán)境下都能穩(wěn)定工作。博主建議非必要不要采用這種方式 ~
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fetch API 上傳進度監(jiān)控</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; // 構(gòu)造一個自定義的 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 發(fā)送 POST 請求 fetch('/upload', { method: 'POST', headers: { // 根據(jù)后端要求設(shè)置合適的 Content-Type // 注意:如果使用 FormData 上傳文件,瀏覽器會自動設(shè)置 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 本身不提供上傳進度事件,上述方法通過包裝文件流來模擬上傳進度,但并非所有瀏覽器都支持這種方式,穩(wěn)定性可能不如 XMLHttpRequest
- 內(nèi)容類型:如果后端要求 multipart/form-data 格式,建議仍采用 XMLHttpRequest 或使用 FormData 對象,因為自定義流方式上傳數(shù)據(jù)可能需要后端特殊處理
4. Axios封裝進度監(jiān)控方案
通過封裝 Axios 請求,可以同時監(jiān)聽上傳和下載的進度,提升用戶體驗,再次之前我們先來看看未封裝前最原始的上傳和下載是如何實現(xiàn)的~
4.1 Axios 上傳進度監(jiān)控
Axios
支持通過配置項 onUploadProgress
來監(jiān)聽上傳進度。以下示例展示了如何使用 Axios
上傳文件,并在頁面上顯示進度信息:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>Axios 上傳進度監(jiān)控</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 事件監(jiān)聽上傳過程,并實時更新進度條和百分比顯示。
4.2 Axios 下載進度監(jiān)控
同理,Axios
還支持 onDownloadProgress
事件來監(jiān)控文件下載進度。下面的示例展示了如何通過 Axios
下載文件并實時顯示下載進度:
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>Axios 下載進度監(jiān)控</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', // 指定響應(yīng)數(shù)據(jù)類型為 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 => { // 創(chuàng)建一個臨時鏈接下載文件 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 請求下載文件,并設(shè)置 responseType 為 blob;
2、通過 onDownloadProgress 事件監(jiān)聽下載進度,并更新進度條;
3、下載完成后利用 Blob 和臨時鏈接觸發(fā)瀏覽器下載文件。
4.3 封裝 Axios 實例
為了在項目中更方便地使用進度監(jiān)控功能,可以將 Axios
進行封裝。例如,我們可以創(chuàng)建一個 Axios 實例,并在請求配置中統(tǒng)一處理上傳和下載進度
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; } }
使用時,在頁面中調(diào)用封裝方法即可,這樣通過封裝后的 Axios 實例,我們可以在項目中更加方便地復(fù)用進度監(jiān)控功能。
5. 特殊場景處理技巧
通常在一些特殊場景下,針對進度監(jiān)控我們還需要一些處理技巧,這里博主分享兩個分別是 分塊上傳監(jiān)控
和 帶寬計算與預(yù)估
5. 1 分塊上傳監(jiān)控
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 帶寬計算與預(yù)估
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(`預(yù)計剩余時間: ${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. 結(jié)語
本篇文章介紹了如何在前端請求中監(jiān)控上傳和下載進度,并提供了完整的前端和后端代碼示例。通過合理運用這些技術(shù),開發(fā)者可以構(gòu)建出具有專業(yè)級進度反饋的Web應(yīng)用,顯著提升用戶在處理大文件傳輸時的體驗。
希望能幫助小伙伴們在項目中更好地處理大文件傳輸,提高用戶體驗!
到此這篇關(guān)于AJAX請求上傳下載進度監(jiān)控指南的文章就介紹到這了,更多相關(guān)AJAX上傳下載內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
妙用Ajax技術(shù)實現(xiàn)局部刷新商品數(shù)量和總價實例代碼
這篇文章主要給大家介紹妙用Ajax技術(shù)實現(xiàn)局部刷新商品數(shù)量和總價實例代碼,非常不錯,需要的朋友一起看看吧2016-05-05解決ajax跨域請求數(shù)據(jù)cookie丟失問題
本文主要是從前端jquery和服務(wù)端php為例,分別使用實例解決ajax跨域請求數(shù)據(jù)cookie丟失問題,推薦給有相同需求的小伙伴們。2015-03-03Ajax獲取數(shù)據(jù)然后顯示在頁面的實現(xiàn)方法
下面小編就為大家?guī)硪黄狝jax獲取數(shù)據(jù)然后顯示在頁面的實現(xiàn)方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08