AJAX請(qǐng)求上傳下載進(jìn)度監(jiān)控實(shí)現(xiàn)方式
1. 前言
在日常 Web 開(kāi)發(fā)中,AJAX(Asynchronous JavaScript and XML)被廣泛用于異步請(qǐng)求數(shù)據(jù),而無(wú)需刷新整個(gè)頁(yè)面。然而,當(dāng)涉及到上傳下載文件或執(zhí)行長(zhǎng)時(shí)間運(yùn)行的任務(wù)時(shí),為了提升用戶(hù)體驗(yàn)通常我們需要顯示執(zhí)行的進(jìn)度條,那么監(jiān)控請(qǐng)求的進(jìn)度就變得尤為重要。
這里博主給大家講解 XMLHttpRequest 和 Fetch API 以及 Axios封裝 在進(jìn)度監(jiān)控上不同的實(shí)現(xiàn)方式

進(jìn)度監(jiān)控的核心場(chǎng)景 :
1. 大文件上傳/下載
2. 實(shí)時(shí)數(shù)據(jù)傳輸(如視頻流)
3. 長(zhǎng)耗時(shí)API請(qǐng)求
4. 用戶(hù)交互反饋優(yōu)化
2. 基于XMLHttpRequest的進(jìn)度監(jiān)控
在 JavaScript 中,XMLHttpRequest 提供了 progress 事件,允許我們監(jiān)聽(tīng)請(qǐng)求的進(jì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(`上傳進(jìn)度: ${percent.toFixed(1)}%`);
}
});
xhr.addEventListener('load', () => {
console.log('上傳完成');
});
xhr.open('POST', '/upload', true);
xhr.send(file);
});
</script>2.2 增強(qiáng)版多事件監(jiān)控
我們還可以集合 progress 進(jìn)行多事件監(jiān)控,如下代碼:
function uploadWithProgress(file) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// 上傳進(jìn)度監(jiān)控
xhr.upload.addEventListener('progress', (e) => {
handleProgress('upload', e);
});
// 下載進(jìn)度監(jiān)控(當(dāng)服務(wù)器返回大數(shù)據(jù)時(shí))
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 的進(jìn)度監(jiān)控
3.1 Fetch API + ReadableStream實(shí)現(xiàn)下載監(jiān)控
Fetch API 本身沒(méi)有直接提供進(jìn)度事件,但我們可以利用 ReadableStream 對(duì)響應(yīng)體進(jìn)行分段讀取,從而計(jì)算已加載的字節(jié)數(shù)。
當(dāng)?shù)谝淮握?qǐng)求鏈接 await fetch(url) 的時(shí)候通過(guò)獲取 headers 中 Content-Length 返回的請(qǐng)求資源總大小,再結(jié)合 response.body.getReader() 來(lái)讀取 body 內(nèi)容來(lái)實(shí)現(xiàn)!
具體參考代碼如下,小伙伴可以根據(jù)自身需求進(jìn)行調(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(`下載進(jìn)度: ${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 上傳進(jìn)度監(jiān)控(偽方案)
Fetch API 原生并不支持上傳進(jìn)度監(jiān)控。不過(guò)可以采用將文件包裝成一個(gè)自定義的 ReadableStream 來(lái)實(shí)現(xiàn) “偽”上傳進(jìn)度監(jiān)控 。
需要注意的是,由于各瀏覽器對(duì) 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 上傳進(jìn)度監(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('請(qǐng)選擇文件');
return;
}
const progressBar = document.getElementById('progressBar');
const progressText = document.getElementById('progressText');
const total = file.size;
let uploaded = 0;
// 構(gòu)造一個(gè)自定義的 ReadableStream 來(lái)包裝文件流
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('讀取文件錯(cuò)誤:', error);
controller.error(error);
});
}
push();
}
});
// 使用 Fetch API 發(fā)送 POST 請(qǐng)求
fetch('/upload', {
method: 'POST',
headers: {
// 根據(jù)后端要求設(shè)置合適的 Content-Type
// 注意:如果使用 FormData 上傳文件,瀏覽器會(huì)自動(dòng)設(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>注意事項(xiàng)
- 上傳進(jìn)度:Fetch API 本身不提供上傳進(jìn)度事件,上述方法通過(guò)包裝文件流來(lái)模擬上傳進(jìn)度,但并非所有瀏覽器都支持這種方式,穩(wěn)定性可能不如 XMLHttpRequest
- 內(nèi)容類(lèi)型:如果后端要求 multipart/form-data 格式,建議仍采用 XMLHttpRequest 或使用 FormData 對(duì)象,因?yàn)樽远x流方式上傳數(shù)據(jù)可能需要后端特殊處理
4. Axios封裝進(jìn)度監(jiān)控方案
通過(guò)封裝 Axios 請(qǐng)求,可以同時(shí)監(jiān)聽(tīng)上傳和下載的進(jìn)度,提升用戶(hù)體驗(yàn),再次之前我們先來(lái)看看未封裝前最原始的上傳和下載是如何實(shí)現(xiàn)的~
4.1 Axios 上傳進(jìn)度監(jiān)控
Axios 支持通過(guò)配置項(xiàng) onUploadProgress 來(lái)監(jiān)聽(tīng)上傳進(jìn)度。以下示例展示了如何使用 Axios 上傳文件,并在頁(yè)面上顯示進(jìn)度信息:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Axios 上傳進(jìn)度監(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 庫(kù) -->
<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('請(qǐng)選擇文件');
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>代碼解釋?zhuān)?/strong>
使用 FormData 對(duì)象包裝上傳的文件;
通過(guò) Axios 的 onUploadProgress 事件監(jiān)聽(tīng)上傳過(guò)程,并實(shí)時(shí)更新進(jìn)度條和百分比顯示。
4.2 Axios 下載進(jìn)度監(jiān)控
同理,Axios 還支持 onDownloadProgress 事件來(lái)監(jiān)控文件下載進(jìn)度。下面的示例展示了如何通過(guò) Axios 下載文件并實(shí)時(shí)顯示下載進(jìn)度:
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Axios 下載進(jìn)度監(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 庫(kù) -->
<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ù)類(lèi)型為 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)建一個(gè)臨時(shí)鏈接下載文件
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>代碼解釋?zhuān)?/strong>
1、我們通過(guò) axios.get 請(qǐng)求下載文件,并設(shè)置 responseType 為 blob;
2、通過(guò) onDownloadProgress 事件監(jiān)聽(tīng)下載進(jìn)度,并更新進(jìn)度條;
3、下載完成后利用 Blob 和臨時(shí)鏈接觸發(fā)瀏覽器下載文件。
4.3 封裝 Axios 實(shí)例
為了在項(xiàng)目中更方便地使用進(jìn)度監(jiān)控功能,可以將 Axios 進(jìn)行封裝。例如,我們可以創(chuàng)建一個(gè) Axios 實(shí)例,并在請(qǐng)求配置中統(tǒng)一處理上傳和下載進(jìn)度
import axios from 'axios';
// 請(qǐng)求配置
const axiosInstance = axios.create({
onUploadProgress: progressEvent => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`上傳進(jìn)度: ${percent}%`);
},
onDownloadProgress: progressEvent => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`下載進(jìn)度: ${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;
}
}使用時(shí),在頁(yè)面中調(diào)用封裝方法即可,這樣通過(guò)封裝后的 Axios 實(shí)例,我們可以在項(xiàng)目中更加方便地復(fù)用進(jìn)度監(jiān)控功能。
5. 特殊場(chǎng)景處理技巧
通常在一些特殊場(chǎng)景下,針對(duì)進(jìn)度監(jiān)控我們還需要一些處理技巧,這里博主分享兩個(gè)分別是 分塊上傳監(jiān)控 和 帶寬計(jì)算與預(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(`分塊進(jìn)度: ${chunkPercent.toFixed(1)}%`);
console.log(`總進(jìn)度: ${totalPercent.toFixed(1)}%`);
}
});
uploadedChunks += chunk.size;
}
}5. 2 帶寬計(jì)算與預(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; // 剩余時(shí)間
console.log(`傳輸速度: ${formatBytes(speed)}/s`);
console.log(`預(yù)計(jì)剩余時(shí)間: ${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é)語(yǔ)
本篇文章介紹了如何在前端請(qǐng)求中監(jiān)控上傳和下載進(jìn)度,并提供了完整的前端和后端代碼示例。通過(guò)合理運(yùn)用這些技術(shù),開(kāi)發(fā)者可以構(gòu)建出具有專(zhuān)業(yè)級(jí)進(jìn)度反饋的Web應(yīng)用,顯著提升用戶(hù)在處理大文件傳輸時(shí)的體驗(yàn)。
希望能幫助小伙伴們?cè)陧?xiàng)目中更好地處理大文件傳輸,提高用戶(hù)體驗(yàn)!
到此這篇關(guān)于AJAX請(qǐng)求上傳下載進(jìn)度監(jiān)控指南的文章就介紹到這了,更多相關(guān)AJAX上傳下載內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Ajax獲得站點(diǎn)文件內(nèi)容實(shí)例
Ajax獲得站點(diǎn)文件內(nèi)容實(shí)例:選擇一部著作,會(huì)通過(guò) Ajax 實(shí)時(shí)獲得相關(guān)的名字,具體實(shí)現(xiàn)如下,感興趣的朋友可以參考下2013-09-09
ajax實(shí)現(xiàn)提交時(shí)校驗(yàn)表單方法
這篇文章主要為大家詳細(xì)介紹了ajax實(shí)現(xiàn)提交時(shí)校驗(yàn)表單方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-02-02
實(shí)現(xiàn)類(lèi)似facebook無(wú)刷新ajax更新
這篇文章主要介紹了實(shí)現(xiàn)類(lèi)似facebook無(wú)刷新ajax更新,需要的朋友可以參考下2014-03-03
妙用Ajax技術(shù)實(shí)現(xiàn)局部刷新商品數(shù)量和總價(jià)實(shí)例代碼
這篇文章主要給大家介紹妙用Ajax技術(shù)實(shí)現(xiàn)局部刷新商品數(shù)量和總價(jià)實(shí)例代碼,非常不錯(cuò),需要的朋友一起看看吧2016-05-05
ajax實(shí)現(xiàn)城市三級(jí)聯(lián)動(dòng)
這篇文章主要為大家詳細(xì)介紹了ajax實(shí)現(xiàn)城市三級(jí)聯(lián)動(dòng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10
AJAX應(yīng)用中必須要掌握的重點(diǎn)知識(shí)(分享)
下面小編就為大家?guī)?lái)一篇AJAX應(yīng)用中必須要掌握的重點(diǎn)知識(shí)(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08
用 ajax 的方法解決網(wǎng)頁(yè)廣告顯示的問(wèn)題
用 ajax 的方法解決網(wǎng)頁(yè)廣告顯示的問(wèn)題...2006-12-12
解決ajax跨域請(qǐng)求數(shù)據(jù)cookie丟失問(wèn)題
本文主要是從前端jquery和服務(wù)端php為例,分別使用實(shí)例解決ajax跨域請(qǐng)求數(shù)據(jù)cookie丟失問(wèn)題,推薦給有相同需求的小伙伴們。2015-03-03
Ajax獲取數(shù)據(jù)然后顯示在頁(yè)面的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇Ajax獲取數(shù)據(jù)然后顯示在頁(yè)面的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08

