+2

series Axios Bài 5: Xử Lý File Dung Lượng Lớn Và Tuyệt Kỹ Theo Dõi Tiến Độ Thời Gian Thực

Trong JavaScript gốc, việc tính toán xem một file đã upload được bao nhiêu phần trăm là một cực hình vì bạn phải tính toán số byte gửi đi qua các sự kiện thô của card mạng. Rất may mắn, Axios đã trừu tượng hóa toàn bộ sự phức tạp đó và cung cấp cho chúng ta hai hàm callback "đáng tiền": onUploadProgress và onDownloadProgress.

Hôm nay, chúng ta sẽ cùng nhau triển khai trọn bộ giải pháp Upload và Download file chuẩn mã nguồn Production.

1. Tuyệt Chiêu Upload File Kèm Thanh Tiến Độ (Upload Progress)

Để gửi một hoặc nhiều file từ Client lên Server, chúng ta không thể dùng định dạng JSON thông thường. Bạn bắt buộc phải đóng gói file vào một đối tượng gọi là FormData.

Dưới đây là hàm xử lý Upload File được viết trong file API của bạn:

import axiosClient from './axiosClient';

const fileApi = {
  uploadAvatar(file, onProgressCallback) {
    const formData = new FormData();
    formData.append('avatar', file); // 'avatar' là tên field mà Backend yêu cầu

    return axiosClient.post('/users/upload-avatar', formData, {
      headers: {
        // Ép định dạng header sang multipart để truyền tải file dữ liệu nhị phân
        'Content-Type': 'multipart/form-data',
      },
      // Hàm callback của Axios tự động kích hoạt mỗi khi có một block dữ liệu được gửi đi thành công
      onUploadProgress: (progressEvent) => {
        // Tính toán phần trăm hoàn thành
        const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
        
        // Bắn phần trăm này về cho giao diện UI cập nhật trạng thái thanh tiến độ
        if (onProgressCallback) {
          onProgressCallback(percentCompleted);
        }
      },
    });
  }
};

Cách sử dụng ở tầng Giao diện (Component):

const handleFileChange = async (event) => {
  const file = event.target.files[0];
  if (!file) return;

  try {
    await fileApi.uploadAvatar(file, (percent) => {
      console.log(`Tiến độ Upload hiện tại: ${percent}%`);
      // Mẹo: Cập nhật biến state `setProgress(percent)` để render ra thanh Progress Bar trên giao diện
    });
    alert('Upload ảnh đại diện thành công!');
  } catch (error) {
    alert('Upload thất bại, vui lòng thử lại!');
  }
};

2. Tuyệt Chiêu Download File Dung Lượng Lớn (Download Progress)

Khi bạn cần tải một file PDF báo cáo tài chính nặng hoặc một file zip từ Server về máy người dùng, nếu bạn chỉ dùng thẻ <a href="link_file">, trình duyệt sẽ tự tải ngầm và người dùng không biết bao giờ mới xong.

Bằng cách dùng Axios để download, bạn vừa theo dõi được tiến độ, vừa có thể đính kèm thêm Token bảo mật vào Header (thứ mà thẻ <a> thông thường không làm được).

Để làm được điều này, bạn cần cấu hình thuộc tính responseType: 'blob':

async function downloadLargeFile(fileUrl, onDownloadProgressCallback) {
  try {
    const response = await axiosClient.get(fileUrl, {
      responseType: 'blob', // Ép dữ liệu trả về ở dạng mảng nhị phân thô (Blob)
      onDownloadProgress: (progressEvent) => {
        // Tương tự upload, tính toán phần trăm dựa trên số byte đã nhận về
        if (progressEvent.total) {
          const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
          onDownloadProgressCallback(percentCompleted);
        }
      }
    });

    // Sau khi tải toàn bộ byte về bộ nhớ đệm, tiến hành ép trình duyệt tải xuống máy user
    const blob = new Blob([response]); // Lưu ý: response lúc này đã qua interceptor trả về response.data ở Bài 3
    const url = window.URL.createObjectURL(blob);
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute('download', 'tai-lieu-cua-toi.zip'); // Tên file muốn lưu
    document.body.appendChild(link);
    link.click();
    link.remove();
  } catch (error) {
    console.error('Lỗi khi download file:', error);
  }
}

Lưu ý chí tử của Senior: Hàm onDownloadProgress chỉ tính toán được phần trăm chính xác nếu phía Backend có trả về Header Content-Length trong HTTP Response. Nếu Backend không cấu hình trả về header này, progressEvent.total sẽ bị trả về giá trị undefined, và bạn chỉ có thể biết được số byte đã tải (progressEvent.loaded) chứ không thể tính được phần trăm tổng thể.

Lời Kết Cho Toàn Bộ Chuỗi Series Axios

Qua 5 bài viết, chúng ta đã cùng nhau đi một hành trình trọn vẹn từ bản chất kiến trúc cho đến những kỹ thuật tối ưu hóa nâng cao nhất của Axios:

  • Bài 1: Hiểu triết lý "lưỡng cư" Isomorphic và sự vượt trội của Axios so với Fetch API.
  • Bài 2: Thiết lập Axios Instance tập trung chuẩn cấu hình dự án Clean Architecture.
  • Bài 3: Làm chủ bộ đánh chặn Interceptors để tự động hóa đính kèm Token và Refresh Token ngầm.
  • Bài 4: Sử dụng AbortController để triệt tiêu lỗi Race Condition và giảm tải cho Server.
  • Bài 5: Làm chủ kỹ thuật truyền tải file dung lượng lớn và tối ưu UX thời gian thực.

Axios không chỉ là một công cụ gọi API đơn thuần, nó là một giải pháp quản trị toàn diện hệ thống mạng ở tầng ứng dụng. Khi bạn làm chủ được 5 bài học này, bạn hoàn toàn tự tin gánh vác phần kết nối mạng của bất kỳ dự án Enterprise quy mô lớn nào.

Cảm ơn bạn đã đồng hành cùng mình qua chuỗi series này!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí