series Axios Bài 4: Kiểm Soát Dòng Chảy Mạng - Tuyệt Chiêu Hủy Request Thừa Bằng AbortController
Trong các phiên bản Axios cũ, chúng ta thường dùng CancelToken. Tuy nhiên, bước sang kỷ nguyên hiện đại, CancelToken đã bị chính thức khai tử (deprecated). Thay vào đó, Axios đã đồng bộ hoàn toàn với tiêu chuẩn của trình duyệt bằng cách sử dụng AbortController.
Hôm nay, chúng ta sẽ học cách dùng AbortController để dập tắt các request thừa và xây dựng một bộ lọc tự động hủy request trùng lặp ngay tại Interceptor.
1. Cơ Chế Hoạt Động Của AbortController
AbortController là một đối tượng có sẵn trong môi trường JavaScript hiện đại. Nó hoạt động như một "nút bấm tự hủy" điều khiển từ xa:
- Bạn tạo ra một cái điều khiển (controller).
- Bạn lấy cái dây tín hiệu từ cái điều khiển đó (controller.signal) nhét vào cấu hình của Axios Request.
- Khi bạn bấm nút controller.abort(), sợi dây tín hiệu sẽ kích hoạt và Axios sẽ ngay lập tức ngắt kết nối, giải phóng băng thông.
2. Thực Chiến 1: Cách Hủy Một Request Cơ Bản
Dưới đây là cách bạn triển khai AbortController trong một component (ví dụ khi người dùng rời khỏi trang - Unmount component):
import axios from 'axios';
// 1. Khởi tạo controller
const controller = new AbortController();
async function fetchHeavyData() {
try {
const response = await axios.get('https://api.example.com/heavy-data', {
signal: controller.signal // 2. Gắn tín hiệu vào request
});
console.log(response.data);
} catch (error) {
if (axios.isCancel(error)) {
// Axios cung cấp hàm kiểm tra xem lỗi này có phải do chủ động hủy hay không
console.log('Request này đã bị chủ động hủy bỏ bởi Developer!');
} else {
// Các lỗi mạng thông thường khác
console.error('Lỗi hệ thống:', error);
}
}
}
// 3. Khi người dùng bấm nút "Hủy" hoặc chuyển trang, ta bấm nút tự hủy:
// controller.abort();
3. Thực Chiến 2: Tự Động Hủy Request Trùng Lặp Tại Interceptor (Cấp độ Cao cấp)
Việc tự tạo AbortController thủ công ở từng file tính năng rất tốn công sức. Đối với các hệ thống lớn, chúng ta thường viết một cơ chế tự động tại axiosClient.js: Nếu một request có cùng URL và cùng Method đang chạy ngầm, hãy tự động hủy request cũ để ưu tiên request mới nhất.
Hãy cập nhật file src/api/axiosClient.js của bạn với bộ nhớ đệm (Cache pending requests) như sau:
import axios from 'axios';
const axiosClient = axios.create({
baseURL: 'https://api.myservice.com/v1',
timeout: 10000,
});
// Tạo một Map để lưu trữ các request đang chờ xử lý
const pendingRequests = new Map();
// Hàm tạo ra một key duy nhất cho mỗi request dựa trên URL và Method
const generateRequestKey = (config) => {
return [config.method, config.url].join('&');
};
// Bộ đánh chặn Request
axiosClient.interceptors.request.use(
(config) => {
const requestKey = generateRequestKey(config);
// Nếu đã có request này đang chạy, tiến hành hủy cái cũ đi
if (pendingRequests.has(requestKey)) {
const currentController = pendingRequests.get(requestKey);
currentController.abort(); // Kích hoạt lệnh hủy
pendingRequests.delete(requestKey); // Xóa khỏi danh sách theo dõi
}
// Tạo một AbortController mới cho request hiện tại
const controller = new AbortController();
config.signal = controller.signal;
pendingRequests.set(requestKey, controller); // Lưu lại vào Map
return config;
},
(error) => Promise.reject(error)
);
// Bộ đánh chặn Response
axiosClient.interceptors.response.use(
(response) => {
// Khi request thành công, xóa nó ra khỏi danh sách theo dõi
const requestKey = generateRequestKey(response.config);
pendingRequests.delete(requestKey);
return response.data;
},
(error) => {
// Khi request thất bại hoặc bị hủy, cũng phải xóa ra khỏi danh sách
if (error.config) {
const requestKey = generateRequestKey(error.config);
pendingRequests.delete(requestKey);
}
// Nếu là lỗi do ta chủ động hủy, trả về một Promise trống hoặc xử lý êm đẹp
if (axios.isCancel(error)) {
return new Promise(() => {}); // Chặn không cho nhảy vào block catch của UI
}
return Promise.reject(error);
}
);
export default axiosClient;
4. Tại Sao Hủy Request Lại Quan Trọng Đối Với Server?
Nhiều bạn lầm tưởng rằng: "Hủy request ở Client thì Server vẫn phải chạy thôi chứ có ích gì?".
Thực tế hoàn toàn ngược lại. Khi Client bấm abort(), kết nối TCP giữa Client và Server sẽ bị ngắt đột ngột.
- Đối với các Web Server hiện đại (nhước Node.js, Go, hoặc Nginx), chúng sẽ ngay lập tức phát hiện ra sự kiện disconnect này.
- Hệ thống sẽ tự động hủy các tiến trình xử lý logic phía sau (như dừng truy vấn Database phức tạp đang chạy dở).
- Điều này giúp giải phóng tài nguyên CPU và RAM cho Server một cách cực kỳ kịp thời, tránh các cuộc tấn công vô tình làm nghẽn hệ thống (Self-inflicted DDoS).
Tóm lại là...
Làm chủ AbortController giúp bạn bảo vệ toàn vẹn dữ liệu hiển thị trên giao diện (chống lỗi Race Condition) và biến ứng dụng Frontend của bạn thành một "công dân mạng" có trách nhiệm, biết tiết kiệm băng thông cho người dùng và giảm tải gánh nặng cho hệ thống Backend.
Bài học tiếp theo sẽ là bài viết cuối cùng khép lại toàn bộ series Axios: Tải file dung lượng lớn và tuyệt kỹ theo dõi tiến độ (Upload/Download Progress Bar) theo thời gian thực.
All rights reserved