Debounce vs Throttle: القرار بالظبط
المشكلة باختصار
أحداث زي input وscroll وresize بتتطلق بمعدل عالي جدًا. الـ scroll ممكن يطلق أكتر من 100 حدث في الثانية على متصفح عادي. لو ربطت بيه دالة تعمل حسابات أو ترسم عناصر DOM، الصفحة هتتلج. ولو ربطت بيه طلب API، هتحرق الـ rate limit في ثواني.
الحل مش إنك تمنع الحدث. الحل إنك تتحكم في معدل استدعاء الدالة المرتبطة بيه. هنا بييجي دور Debounce و Throttle.
Debounce: استنى لحد ما يهدا
Debounce بيقولك "نفذ الدالة بس لما يعدي وقت معين من غير ما يتكرر الحدث". لو المستخدم لسه بيكتب، الـ timer بيتعمل reset. الدالة بتشتغل مرة واحدة بعد ما يبطل.
الاستخدام الأشهر: حقل بحث يضرب API. مش منطقي تبعت طلب مع كل حرف. منطقي تستنى 300ms بعد آخر حرف، وبعدين تبعت طلب واحد بالنص الكامل.
function debounce(fn, delay) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
// الاستخدام الفعلي
const searchInput = document.getElementById("search");
const handleSearch = debounce(async (value) => {
const res = await fetch(`/api/search?q=${encodeURIComponent(value)}`);
const data = await res.json();
renderResults(data);
}, 300);
searchInput.addEventListener("input", (e) => handleSearch(e.target.value));
النتيجة القابلة للقياس: مستخدم بيكتب كلمة من 10 حروف في ثانية ونص. بدون debounce: 10 طلبات HTTP. مع debounce(300ms): طلب واحد فقط. توفير 90% من الطلبات، والـ backend بياخد نفس.
Throttle: نفذ بانتظام مش أكتر من كدا
Throttle بيختلف بالظبط في النية. هو بيقولك "نفذ الدالة بحد أقصى مرة كل X ملي ثانية، حتى لو الحدث اتطلق 1000 مرة في الفترة دي". مش بيستنى السكون، بيفرض إيقاع ثابت.
الاستخدام الأشهر: scroll handler يحدث موقع عنصر في الصفحة، أو يحسب لو المستخدم وصل لآخر الـ list عشان يعمل pagination. لو استنيت السكون (debounce)، المستخدم هيوصل آخر الصفحة ومفيش حاجة هتحصل لحد ما يبطل scroll. ده سلوك غلط.