Hệ thống lưu Cache phản hồi API trong JavaScript
Khi phát triển ứng dụng web, tối ưu hóa các cuộc gọi API là điều cần thiết để cải thiện hiệu suất. Một chiến lược hiệu quả là triển khai cơ chế lưu cache, giúp lưu trữ phản hồi API trong một khoảng thời gian nhất định. Cách tiếp cận này giúp:
- Giảm bớt các yêu cầu mạng không cần thiết
- Giảm tải cho máy chủ
- Cải thiện trải nghiệm người dùng bằng cách phản hồi nhanh hơn
Trong bài viết này, chúng ta sẽ khám phá cách xây dựng một hệ thống lưu cache API linh hoạt bằng JavaScript, có thể dễ dàng tích hợp vào bất kỳ ứng dụng nào.
Vấn đề nan giải
Hãy tưởng tượng bạn đang phát triển một ứng dụng web thường xuyên gọi API từ bên ngoài. Việc gửi đi các yêu cầu lặp lại trong khoảng thời gian ngắn là không hiệu quả vì:
- Tiêu tốn băng thông mạng: Mỗi lần gọi API tiêu tốn dữ liệu và tăng độ trễ.
- Tăng tải máy chủ: Các yêu cầu lặp lại có thể làm căng thẳng tài nguyên của máy chủ.
- Giới hạn tốc độ: Nhiều API áp đặt giới hạn số lần gọi, có thể dẫn đến việc bị tạm khóa.
- Trải nghiệm người dùng kém: Người dùng phải chờ đợi khi API được gọi lại mà không có lý do chính đáng.
Giải pháp: Hệ thống Cache dựa trên thời gian
Chúng ta cần một cơ chế có thể:
- Lưu cache phản hồi API trong một khoảng thời gian nhất định
- Trả về dữ liệu đã cache nếu yêu cầu giống nhau được gửi trong khoảng thời gian này
- Gửi yêu cầu mới sau khi cache hết hạn
- Hỗ trợ nhiều endpoint API khác nhau và các cấu hình request khác nhau
Triển khai
Giải pháp này sẽ bao gồm 3 thành phần chính:
- Tạo khóa cache duy nhất để xác định từng yêu cầu API
- Chức năng gửi yêu cầu API để lấy dữ liệu từ máy chủ
- Hàm chính thực hiện cơ chế cache để lưu và lấy dữ liệu từ cache
1. Tạo khóa Cache duy nhất
Trước tiên, chúng ta cần một cách để nhận diện các yêu cầu API giống nhau. Dưới đây là hàm tạo khóa cache dựa trên endpoint và cấu hình request:
const generateCachedKey = (path, config) => {
const key = Object.keys(config)
.sort((a, b) => a.localeCompare(b))
.map((k) => k + ":" + config[k].toString())
.join("&");
return path + key;
};
Chức năng của hàm này:
- Nhận đầu vào là đường dẫn API (
path
) và cấu hình request (config
) - Sắp xếp các khóa trong config theo thứ tự bảng chữ cái để đảm bảo tính nhất quán
- Chuyển cấu hình request thành một chuỗi duy nhất
- Kết hợp
path
và chuỗi cấu hình để tạo ra một khóa duy nhất
2. Gửi yêu cầu API
Tiếp theo, chúng ta cần một hàm để gửi yêu cầu API thực tế:
const fetchApi = async (path, config) => {
try {
let response = await fetch(path, config);
response = await response.json();
return response;
} catch (e) {
console.log("error " + e);
}
return null;
};
Chức năng của hàm này:
- Sử dụng fetch để gửi yêu cầu HTTP
- Chuyển đổi phản hồi thành JSON
- Xử lý lỗi nếu có lỗi xảy ra
- Trả về dữ liệu phản hồi hoặc null nếu có lỗi
3. Cơ chế Cache
Dưới đây là hàm chính, nơi triển khai logic cache:
const cachedApiCall = (time) => {
const cache = {};
return async function (path, config = {}) {
const key = generateCachedKey(path, config);
let entry = cache[key];
if (!entry || Date.now() > entry.expiryTime) {
console.log("making new api call");
try {
const value = await fetchApi(path, config);
cache[key] = { value, expiryTime: Date.now() + time };
} catch (e) {
console.log(error);
}
}
return cache[key].value;
};
};
Chức năng này:
- Lấy một tham số
time
chỉ định thời lượng bộ nhớ đệm tính bằng mili giây - Tạo một đối tượng
cache
riêng tư bằng cách sử dụng closure - Trả về một hàm xử lý logic lưu trữ đệm
- Kiểm tra xem mục nhập bộ nhớ cache hợp lệ có tồn tại cho yêu cầu hay không
- Thực hiện lệnh gọi API mới nếu cần và cập nhật bộ nhớ đệm
- Đặt thời gian hết hạn cho phản hồi được lưu trong bộ nhớ đệm
- Trả về giá trị được lưu trong bộ nhớ đệm
Ví dụ sử dụng
Sau đây là cách sử dụng chức năng lưu trữ đệm:
const call = cachedApiCall(2000); // Cache responses for 2 seconds
// First call - makes an API request and caches the response
call("https://www.example.com", {}).then((a) =>
console.log(a)
);
// Second call (800ms later) - returns the cached response
setTimeout(() => {
call("https://www.example.com", {}).then((a) =>
console.log(a)
);
}, 800);
// Third call (2500ms later) - cache has expired, makes a fresh API call
setTimeout(() => {
call("https://www.example.com", {}).then((a) =>
console.log(a)
);
}, 2500);
// Console: "making new api call"
Lợi ích & Những điều cần lưu ý
1. Lợi ích:
- Giảm bớt lưu lượng mạng: Hạn chế số lần gọi API không cần thiết
- Cải thiện hiệu suất: Phản hồi nhanh hơn nhờ lấy dữ liệu từ cache
- Linh hoạt: Dễ dàng tùy chỉnh thời gian cache dựa trên yêu cầu của ứng dụng
- Đa năng: Có thể áp dụng cho bất kỳ API nào
2. Những điều cần lưu ý:
- Dùng nhiều bộ nhớ: Nếu ứng dụng có quá nhiều API, cache có thể chiếm nhiều bộ nhớ
- Dữ liệu không được cập nhật ngay lập tức: Nếu API thay đổi liên tục, cần giảm thời gian cache hoặc cập nhật thủ công
- Xử lý lỗi: Cần có cơ chế thử lại (retry) khi yêu cầu API bị lỗi
- Xóa cache: Nên có phương thức để xóa hoặc làm mới dữ liệu khi cần
Nâng cấp hệ thống Cache
Để làm cho hệ thống này mạnh mẽ hơn, có thể bổ sung các tính năng:
- Giới hạn kích thước cache: Triển khai thuật toán LRU (Least Recently Used)
- Lưu cache vào localStorage: Giữ lại dữ liệu ngay cả khi tải lại trang
- Tránh gửi yêu cầu trùng lặp: Hợp nhất các yêu cầu đến cùng một tài nguyên
- Tiền tải dữ liệu (Prefetching): Lưu trước các tài nguyên mà người dùng có thể cần đến
Kết luận
Hệ thống cache API này có thể giúp cải thiện hiệu suất và trải nghiệm người dùng bằng cách giảm số lần gọi API không cần thiết.
- Tùy chỉnh thời gian cache hợp lý để cân bằng giữa hiệu suất và độ chính xác của dữ liệu
- Dễ dàng tích hợp vào bất kỳ ứng dụng JavaScript nào
- Có thể mở rộng với nhiều tính năng nâng cao như LRU, localStorage, v.v.
Hãy kết hợp caching với các chiến lược tối ưu hóa API khác như gộp yêu cầu (batching), nén dữ liệu (compression), và tải có chọn lọc (selective loading) để đạt hiệu quả tối đa.
All rights reserved