مستوى المقال: مبتدئ
Debouncing وThrottling: ليه شريط البحث بيرسل 8400 طلب في الدقيقة؟
لو شريط البحث في موقعك بيرسل request لكل حرف يكتبه المستخدم، انت بتدفع تكلفة 700 ضِعف اللي محتاجها فعلاً. مع 200 مستخدم نشط في وقت واحد، الـ API بياخد 8400 طلب في الدقيقة بدل 12 طلب فعلي. 7 سطور JavaScript بـ debounce بيحلّوا المشكلة كاملة بدون ما تلمس الـ backend.
المشكلة باختصار
كل event من نوع input أو scroll أو resize بيتنفّذ عشرات المرات في الثانية. لو ربطت كل واحد منهم بطلب AJAX أو حساب ثقيل، CPU المتصفح بيتعب والسيرفر بيتكدّس. النتيجة: تجربة بطيئة، فاتورة DB أعلى، واحتمال إن rate-limit يضرب المستخدم الحقيقي قبل ما يخلّص بحثه.
الافتراض هنا: عندك تطبيق ويب فيه search input أو scroll handler، ومحرّك الـ backend بياخد كل request جدّ ويعمل query على DB. لو كل event عندك بيتعامل مع state محلي بس، المقال ده بيهمّك أقل.
مثال البقّال: ليه debounce بيشتغل
تخيّل بقّال بياخد طلبيات على التليفون. كل مرة التليفون يدقّ، يقفل اللي قاعد يحضّره ويبدأ من الأول مع الزبون الجديد. لو زبون واحد بيغيّر طلبه كل تانيتين، البقّال هيقعد ساعة من غير ما يخلّص ولا طلب. الحل اللي بيستخدمه أي بقّال شاطر: "هستنّى 3 ثواني بعد آخر مكالمة، لو ما رنّش تاني هبدأ أحضّر الطلب الأخير".
ده بالظبط اللي debounce بيعمله للكود. كل مرة المستخدم يكتب حرف، الـ debounce بيلغي العدّ السابق ويبدأ عدّ جديد. لمّا يسكت 300 مللي ثانية، الدالة بتشتغل مرة واحدة بآخر قيمة.
تعريف علمي: debounce vs throttle
الفرق بين الاتنين بسيط لكنه حاسم في الاختيار:
- Debounce: نفّذ الدالة مرة واحدة بعد ما الـ events تتوقف لمدة X مللي ثانية. مناسب لـ search input، form validation، window resize.
- Throttle: نفّذ الدالة مرة واحدة كل X مللي ثانية بالظبط، مهما كان عدد الـ events اللي حصلت. مناسب لـ scroll، mousemove، analytics tracking.
القاعدة العملية: لو محتاج "آخر قيمة فقط بعد ما المستخدم يخلّص" استخدم debounce. لو محتاج "تحديث منتظم على فترات ثابتة" استخدم throttle.
كود debounce فعلي في 7 سطور
function debounce(fn, delay = 300) {
let timeoutId;
return function (...args) {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => fn.apply(this, args), delay);
};
}
const searchInput = document.querySelector('#search');
const handleSearch = debounce((event) => {
fetch(`/api/search?q=${event.target.value}`);
}, 300);
searchInput.addEventListener('input', handleSearch);