Trong phát triển web hàng ngày, AJAX (Asynchronous JavaScript and XML) được sử dụng rộng rãi để yêu cầu dữ liệu bất đồng bộ mà không cần phải tải lại toàn bộ trang. Bài viết này chủ yếu giới thiệu hướng dẫn giám sát tiến độ tải lên và tải xuống của yêu cầu AJAX, các bạn có thể tham khảo nếu cần.
1. Lời mở đầu
Trong phát triển web hàng ngày, AJAX (Asynchronous JavaScript and XML) được sử dụng rộng rãi để yêu cầu dữ liệu bất đồng bộ mà không cần phải tải lại toàn bộ trang. Tuy nhiên, khi xử lý tải lên hoặc tải xuống tệp, hoặc thực hiện các tác vụ chạy lâu dài, để cải thiện trải nghiệm người dùng, chúng ta thường cần hiển thị thanh tiến độ. Do đó, việc giám sát tiến độ của yêu cầu trở nên vô cùng quan trọng.
Bài viết này sẽ giải thích các phương thức khác nhau trong việc giám sát tiến độ sử dụng XMLHttpRequest, Fetch API và cách đóng gói với Axios.
Các tình huống chính cần giám sát tiến độ:
-
Tải lên/tải xuống tệp lớn
-
Truyền tải dữ liệu thời gian thực (ví dụ: video stream)
-
Yêu cầu API kéo dài
-
Tối ưu hóa phản hồi người dùng
2. Giám sát tiến độ dựa trên XMLHttpRequest
Trong JavaScript, XMLHttpRequest cung cấp sự kiện progress
, cho phép chúng ta theo dõi tiến độ của yêu cầu.
2.1 Phiên bản cơ bản giám sát tải lên tệp
<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 Phiên bản nâng cao theo dõi nhiều sự kiện
Chúng ta cũng có thể kết hợp sự kiện progress
để theo dõi nhiều sự kiện, như mã dưới đây:
function uploadWithProgress(file) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
// Giám sát tiến trình tải lên
xhr.upload.addEventListener(‘progress’, (e) => {
handleProgress(‘upload’, e);
});
// Giám sát tiến trình tải xuống (khi máy chủ trả về dữ liệu lớn)
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. Giám sát tiến trình dựa trên Fetch API
3.1 Giám sát tải xuống bằng Fetch API + ReadableStream
Fetch API bản thân không cung cấp sự kiện tiến trình trực tiếp, nhưng chúng ta có thể sử dụng ReadableStream để đọc phân đoạn của nội dung phản hồi, từ đó tính toán số byte đã tải xuống.
Khi yêu cầu liên kết lần đầu tiên await fetch(url)
, chúng ta có thể lấy kích thước tổng của tài nguyên yêu cầu từ Content-Length
trong headers, sau đó kết hợp với response.body.getReader()
để đọc nội dung body nhằm thực hiện giám sát tiến trình!
Cụ thể tham khảo mã dưới đây, các bạn có thể điều chỉnh theo nhu cầu của mình:
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(`Tiến độ tải về: ${percent.toFixed(1)}%`);
}
const blob = new Blob(chunks);
return blob;
}
// Ví dụ sử dụng
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 Giám sát tiến trình tải lên bằng Fetch API (Giải pháp giả lập)
Fetch API không hỗ trợ giám sát tiến trình tải lên trực tiếp. Tuy nhiên, có thể sử dụng một ReadableStream tùy chỉnh để “giả lập” việc giám sát tiến trình tải lên.
Cần lưu ý rằng, do mức độ hỗ trợ xử lý luồng Request của các trình duyệt khác nhau, phương pháp này có thể không hoạt động ổn định trên tất cả các môi trường. Người viết bài khuyên không nên sử dụng phương pháp này trừ khi thật sự cần thiết.
<!DOCTYPE html>
<html lang=”vi”>
<head>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
<title>Giám sát tiến trình tải lên với Fetch API</title>
</head>
<body>
<input type=”file” id=”fileInput”>
<button onclick=”uploadFile()”>Tải lên tệp</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(‘Vui lòng chọn tệp’);
return;
}
const progressBar = document.getElementById(‘progressBar’);
const progressText = document.getElementById(‘progressText’);
const total = file.size;
let uploaded = 0;
// Tạo một ReadableStream tùy chỉnh để bao bọc dòng tệp
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(‘Lỗi đọc tệp:’, error);
controller.error(error);
});
}
push();
}
});
// Sử dụng Fetch API để gửi yêu cầu POST
fetch(‘/upload’, {
method: ‘POST’,
headers: {
// Cài đặt Content-Type phù hợp theo yêu cầu của backend
// Lưu ý: Nếu sử dụng FormData để tải tệp, trình duyệt sẽ tự động cài đặt multipart/form-data
‘Content-Type’: ‘application/octet-stream’
},
body: stream
})
.then(response => response.json())
.then(data => {
console.log(‘Tải lên thành công:’, data);
alert(‘Tải lên hoàn tất’);
})
.catch(error => {
console.error(‘Tải lên thất bại:’, error);
alert(‘Tải lên thất bại’);
});
}
</script>
</body>
</html>
4. Giải pháp giám sát tiến độ với Axios
Bằng cách đóng gói yêu cầu Axios, chúng ta có thể theo dõi đồng thời tiến độ tải lên và tải xuống, cải thiện trải nghiệm người dùng. Trước khi đi vào chi tiết, chúng ta hãy cùng xem cách tải lên và tải xuống được thực hiện trong phiên bản gốc mà không có đóng gói.
4.1 Giám sát tiến độ tải lên với Axios
Axios hỗ trợ cấu hình onUploadProgress
để theo dõi tiến độ tải lên. Ví dụ dưới đây cho thấy cách sử dụng Axios để tải lên tệp và hiển thị thông tin tiến độ trên trang:
<!DOCTYPE html>
<html lang=”zh”>
<head>
<meta charset=”UTF-8″>
<title>Giám sát tiến độ tải lên với Axios</title>
</head>
<body>
<h2>Tải lên tệp (Axios)</h2>
<input type=”file” id=”fileInput”>
<button onclick=”uploadFile()”>Tải lên tệp</button>
<br>
<progress id=”uploadProgress” value=”0″ max=”100″ style=”width: 300px;”></progress>
<span id=”uploadText”>0%</span>
<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(‘Vui lòng chọn tệp’);
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(‘Tải lên thành công’);
console.log(response.data);
})
.catch(error => {
alert(‘Tải lên thất bại’);
console.error(error);
});
}
</script>
</body>
</html>
Giải thích mã:
Sử dụng đối tượng FormData để đóng gói tệp tin tải lên;
Thông qua sự kiện onUploadProgress của Axios để theo dõi quá trình tải lên và cập nhật thanh tiến trình và hiển thị phần trăm thời gian thực.
4.2 Giám sát tiến độ tải xuống với Axios
Tương tự, Axios cũng hỗ trợ sự kiện onDownloadProgress để theo dõi tiến độ tải xuống tệp tin. Ví dụ dưới đây cho thấy cách sử dụng Axios để tải xuống tệp tin và hiển thị tiến độ tải xuống theo thời gian thực:
<!DOCTYPE html>
<html lang=”zh”>
<head>
<meta charset=”UTF-8″>
<title>Giám sát tiến độ tải xuống Axios</title>
</head>
<body>
<h2>Tải xuống tệp (Axios)</h2>
<button onclick=”downloadFile()”>Tải xuống tệp</button>
<br>
<progress id=”downloadProgress” value=”0″ max=”100″ style=”width: 300px;”></progress>
<span id=”downloadText”>0%</span>
<script src=”https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js”></script>
<script>
function downloadFile() {
axios.get(‘/download’, {
responseType: ‘blob’, // Chỉ định loại dữ liệu phản hồi là 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 => {
// Tạo liên kết tạm thời để tải xuống tệp
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(‘Tải xuống thất bại’);
console.error(error);
});
}
</script>
</body>
</html>
Giải thích mã nguồn:
-
Chúng ta sử dụng
axios.get
để tải tệp và thiết lậpresponseType
là blob; -
Sử dụng sự kiện
onDownloadProgress
để theo dõi tiến độ tải xuống và cập nhật thanh tiến độ; -
Sau khi tải xuống hoàn tất, sử dụng Blob và liên kết tạm thời để kích hoạt trình duyệt tải xuống tệp.
4.3 Đóng gói đối tượng Axios
Để sử dụng tính năng theo dõi tiến độ dễ dàng hơn trong dự án, chúng ta có thể đóng gói Axios. Ví dụ, chúng ta có thể tạo một đối tượng Axios và xử lý đồng bộ tiến độ tải lên và tải xuống trong cấu hình yêu cầu.
import axios from ‘axios’;
// Cấu hình yêu cầu
const axiosInstance = axios.create({
onUploadProgress: progressEvent => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Tiến độ tải lên: ${percent}%`);
},
onDownloadProgress: progressEvent => {
const percent = Math.round(
(progressEvent.loaded * 100) / progressEvent.total
);
console.log(`Tiến độ tải xuống: ${percent}%`);
}
});
// Đóng gói phương thức tải lên
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(‘Tải lên thất bại:’, error);
throw error;
}
}
Khi sử dụng, chỉ cần gọi phương thức đã đóng gói trên trang, như vậy thông qua instance Axios đã đóng gói, chúng ta có thể dễ dàng tái sử dụng tính năng giám sát tiến độ trong dự án.
5.Xử lý tình huống đặc biệt
Thông thường, trong một số tình huống đặc biệt, để giám sát tiến độ, chúng ta cần một số kỹ thuật xử lý. Ở đây, tác giả chia sẻ hai kỹ thuật, đó là Giám sát tải lên theo phân khối và Tính toán và ước tính băng thông.
5.1 Giám sát tải lên theo phân khối
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(`Tiến độ từng phần: ${chunkPercent.toFixed(1)}%`);
console.log(`Tiến độ tổng thể: ${totalPercent.toFixed(1)}%`);
}
});
uploadedChunks += chunk.size;
}
}
5.2 Tính toán và ước tính băng thông
let startTime;
let lastLoaded = 0;
xhr.upload.addEventListener(‘progress’, e => {
if (!startTime) startTime = Date.now();
const currentTime = Date.now();
const elapsed = (currentTime – startTime) / 1000; // Giây
const speed = e.loaded / elapsed; // bytes/s
const remaining = (e.total – e.loaded) / speed; // Thời gian còn lại
console.log(`Tốc độ truyền tải: ${formatBytes(speed)}/s`);
console.log(`Thời gian còn lại dự kiến: ${remaining.toFixed(1)} giây`);
});
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]}`;
}
Dưới đây là bản dịch tiếng Việt của nội dung:
6.Kết luận
Bài viết này đã giới thiệu cách theo dõi tiến độ tải lên và tải xuống trong các yêu cầu frontend và cung cấp các ví dụ mã nguồn đầy đủ cho cả frontend và backend. Bằng cách áp dụng hợp lý các kỹ thuật này, các nhà phát triển có thể xây dựng ứng dụng web với phản hồi tiến độ chuyên nghiệp, giúp nâng cao trải nghiệm người dùng khi xử lý các tệp lớn.
Hy vọng sẽ giúp các bạn xử lý việc truyền tải tệp lớn trong dự án và nâng cao trải nghiệm người dùng!
Đến đây, bài viết về hướng dẫn theo dõi tiến độ tải lên và tải xuống trong yêu cầu AJAX đã kết thúc. Để tìm hiểu thêm về các nội dung liên quan đến AJAX tải lên và tải xuống, vui lòng tìm kiếm các bài viết trước đây của Script Home hoặc tiếp tục duyệt các bài viết liên quan bên dưới. Hy vọng mọi người sẽ tiếp tục ủng hộ.
Tài nguyên này được người dùng tải lên và nội dung được lấy từ Internet. Trang web này chỉ giới thiệu miễn phí để học tập và chia sẻ. Nếu có bất kỳ vấn đề bản quyền hoặc vấn đề nào khác, vui lòng liên hệ với biên tập viên của trang web này để xử lý!
Lưu ý quan trọng: : Nếu phần mềm liên quan đến thanh toán, thành viên, nạp tiền, v.v., thì đây là những hành động của nhà phát triển phần mềm hoặc công ty sở hữu phần mềm đó và không liên quan gì đến trang web này. Cư dân mạng cần phải tự đưa ra phán đoán của mình.