Debounce vs Throttle: 2 Kỹ Thuật Kiểm Soát Sự Kiện Mà Dev Nào Cũng Cần
Các sự kiện như cuộn trang (scroll), thay đổi kích thước cửa sổ (resize), hay gõ phím (input) có thể kích hoạt handler hàng trăm lần mỗi giây. Nếu không kiểm soát, CPU quá tải và giao diện giật lag không thể tránh khỏi. Debounce và Throttle là hai giải pháp cho vấn đề này — nhưng chúng hoạt động theo cách hoàn toàn khác nhau.

Trung Vũ Hoàng
Tác giả
1. Debounce — "Đợi Khi Nào Xong Rồi Mới Làm"
Debounce buộc hàm phải chờ một khoảng thời gian nhất định sau lần kích hoạt cuối cùng mới thực thi. Nếu sự kiện xảy ra lại trong lúc chờ, đồng hồ đếm ngược bị reset về 0.
Hình dung như thang máy tự động: nó không đóng cửa ngay khi bạn bước vào. Nó đợi 5 giây — nếu có người thêm bước vào ở giây thứ 3, nó đợi thêm 5 giây nữa. Chỉ đóng khi không còn ai bước vào trong 5 giây liên tiếp.
function debounce(func, delay) {
let timeoutId;
return function(...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
func.apply(this, args);
}, delay);
};
}
// Chỉ gọi API sau khi người dùng ngừng gõ 500ms
const searchHandler = debounce(() => fetchResults(query), 500);2. Throttle — "Cứ Đều Đặn Mà Chạy"
Throttle đảm bảo hàm chỉ thực thi tối đa một lần trong một khoảng thời gian cố định — bất kể sự kiện xảy ra bao nhiêu lần trong khoảng đó.
Giống như xe bus định kỳ: cứ 15 phút chạy một chuyến. Dù 1 hay 100 người đứng chờ, xe vẫn chỉ xuất phát đúng nhịp độ đã định.
function throttle(func, limit) {
let lastRan;
return function(...args) {
if (!lastRan) {
func.apply(this, args);
lastRan = Date.now();
} else if (Date.now() - lastRan >= limit) {
func.apply(this, args);
lastRan = Date.now();
}
};
}
// Kiểm tra scroll position tối đa 1 lần/giây
const scrollHandler = throttle(() => checkScrollPosition(), 1000);3. So Sánh Trực Tiếp
Thời điểm thực thi
Debounce: Sau khi sự kiện tạm dừng (trailing edge).
Throttle: Theo nhịp đều đặn trong suốt thời gian sự kiện diễn ra.
Tần suất gọi hàm
Debounce: Có thể chỉ gọi 1 lần dù người dùng thao tác liên tục trong nhiều giây.
Throttle: Gọi N lần đều đặn tương ứng với tổng thời gian và khoảng delay.
Mục tiêu UX
Debounce: Kết quả chính xác sau khi thao tác hoàn tất.
Throttle: Phản hồi liên tục nhưng ở mức độ kiểm soát được.
4. Khi Nào Dùng Cái Nào?
Chọn Debounce khi:
Search autocomplete: Đợi người dùng ngừng gõ trước khi gọi API — tránh lãng phí hàng chục request không cần thiết.
Validate form real-time: Kiểm tra email đã tồn tại chưa sau khi người dùng hoàn tất nhập, không phải mỗi ký tự.
Window resize: Chỉ tính lại layout khi người dùng đã kéo xong kích thước cửa sổ.
Chọn Throttle khi:
Infinite scroll: Kiểm tra xem người dùng đã gần cuối trang chưa — cứ 100–200ms kiểm tra một lần là đủ.
Mouse tracking / Canvas drawing: Theo dõi tọa độ chuột để vẽ hoặc hiệu ứng, cần phản hồi liên tục nhưng có giới hạn.
Ngăn spam nút Submit: Người dùng click nhiều lần — chỉ xử lý đúng nhịp định sẵn.
Dùng sai kỹ thuật = UX tệ: Dùng Debounce cho mouse tracking khiến nét vẽ trên canvas chỉ xuất hiện khi người dùng dừng tay — ứng dụng trông như bị đơ hoàn toàn.
5. Lưu Ý Khi Dùng Trong React
Khi dùng Debounce/Throttle trong React component, bọc hàm bằng useCallback để tránh tạo lại hàm mới sau mỗi render — điều này sẽ reset đồng hồ đếm ngược và làm vô hiệu hóa toàn bộ logic:
const debouncedSearch = useCallback(
debounce((query) => fetchResults(query), 500),
[] // Chỉ tạo hàm debounce một lần
);Trong môi trường production, khuyến nghị dùng _.debounce và _.throttle từ Lodash — hai hàm này xử lý các edge case phức tạp (leading/trailing edge, cancel...) mà implementation tự viết dễ bỏ sót.
Tổng Kết
Debounce và Throttle là công cụ cơ bản trong bộ toolkit của mọi web developer. Quy tắc nhớ nhanh:
Debounce → "Hành động chỉ khi người dùng dừng lại."
Throttle → "Hành động đều đặn dù người dùng không dừng."
Rà soát lại ứng dụng của bạn — đâu là những sự kiện đang tiêu tốn tài nguyên không cần thiết? Đó chính xác là nơi hai kỹ thuật này phát huy giá trị.
Câu hỏi thường gặp
Bài viết liên quan

Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Mọi ứng dụng React thực tế đều phải giao tiếp với API bất đồng bộ. Nếu quản lý kém, ứng dụng dễ rơi vào các vấn đề như UI bị đơ, race condition, rò rỉ bộ nhớ, hay hiển thị dữ liệu cũ. Zustand giải quyết bài toán này với một cú pháp cực kỳ đơn giản — định nghĩa async actions trực tiếp trong store, không cần middleware phức tạp như Redux Thunk hay Saga.

Promise Nâng Cao: all, allSettled, race, any — Khi Nào Dùng Cái Nào?
Nếu bạn chỉ dùng async/await tuần tự, ứng dụng của bạn đang bỏ phí tiềm năng hiệu suất — mỗi request phải chờ request trước xong mới bắt đầu. Các phương thức tĩnh của Promise (all, allSettled, race, any) cho phép bạn điều phối nhiều tác vụ bất đồng bộ song song theo chiến lược phù hợp từng bài toán.

Zustand Async: 5 Cách Xử Lý Bất Đồng Bộ Hiệu Quả Trong React
Mọi ứng dụng React thực tế đều phải giao tiếp với API bất đồng bộ. Nếu quản lý kém, ứng dụng dễ rơi vào các vấn đề như UI bị đơ, race condition, rò rỉ bộ nhớ, hay hiển thị dữ liệu cũ. Zustand giải quyết bài toán này với một cú pháp cực kỳ đơn giản — định nghĩa async actions trực tiếp trong store, không cần middleware phức tạp như Redux Thunk hay Saga.