لو الـ search box بتاعك بيبعت طلب مع كل حرف يضغطه المستخدم، انت بتولّد 15–20 طلب في الثانية لخادم مش محتاج يشوف إلا آخر طلب منهم. Debounce بيخلّيه طلب واحد. Throttle بيخلّيه 2–3 طلبات. الاتنين بيبصوا كأنهم نفس الحل، لكنهم بيحلّوا سيناريوهين مختلفين تمامًا، واختيار الغلط بيكلّفك إمّا تجربة مستخدم مهتزة أو فاتورة API متضخّمة.
Debounce vs Throttle: الفرق اللي بيوفّر 95% من طلبات الـ API
المشكلة باختصار
المستخدم بيكتب كلمة "react hooks" في حقل البحث. ده 11 حرف. بدون أي حماية، الـ handler بينطلق 11 مرة، وبيبعت 11 طلب AJAX للسيرفر. 10 منهم بتبقى ملغية فعليًا لما الكلمة اللي بعدها توصل. النتيجة: ضغط زيادة على الباك إند، bill أعلى على AWS API Gateway، وفي حالات كتير race condition حيث الطلب القديم بيرجع بعد الجديد ويكتب فوقه في الـ UI.
نفس المشكلة موجودة في حدث scroll: بيطلق بمعدّل 60 مرة في الثانية على الأجهزة الحديثة، كل مرة بتشغّل حسابات ثقيلة. الفرق إن scroll مش بيلغي الحسابات القديمة — بتتراكم وتعلّق الـ main thread.
الفكرة بمثال بسيط: المصعد والمدخّن
قبل التعريف العلمي، ركّز على مشهدين من حياتك العادية:
Debounce = المصعد. لو دخلت المصعد وضغطت زرار الطابق، المصعد بيستنى 3 ثواني قبل ما يقفل الباب. لو حد تاني دخل قبل ما يقفل، العدّاد بيتصفّر من الأول. كل ما حد يدخل جديد، الـ timer بيتعاد. المصعد بيتحرّك بس لما يعدّي 3 ثواني من غير ما يدخل حد جديد. هنا "الحدث" هو دخول راكب، و"الفعل" هو تحريك المصعد.
Throttle = المدخّن اللي بيحترم نفسه. قرّر "هدخّن سيجارة كل ساعة". مهما زاد توتره، ومهما تكرّر إحساسه إنه محتاج واحدة، هو ماشي على نظام صارم: سيجارة واحدة كل 60 دقيقة بالظبط. الأحداث بتيجي بأي سرعة، لكن الفعل بينطلق بمعدّل ثابت مضمون.
لمّا نرجع للكود: debounce بيقول "استنى للضوضاء تهدى، نفّذ مرة واحدة بعد ما تخلص". throttle بيقول "مهما كانت الضوضاء، أنا هنفّذ كل X ميلي ثانية، لا أكتر ولا أقل".
التعريف العلمي الدقيق
Debounce هي higher-order function بتاخد دالة ووقت انتظار wait، وبترجع دالة مغلّفة. الدالة المغلّفة لما تتنادى، بتلغي أي استدعاء سابق معلّق عبر clearTimeout وبتجدول استدعاء جديد عبر setTimeout بعد wait ميلي ثانية. النتيجة: الدالة الأصلية بتتنفّذ مرة واحدة فقط بعد ما يعدّي وقت wait كامل بدون استدعاء جديد.
Throttle هي higher-order function بتاخد دالة ووقت limit. الدالة المغلّفة بتسمح بتنفيذ الأصلية في أول استدعاء، وتبلّك كل استدعاء لاحق لحد ما يعدّي limit ميلي ثانية من آخر تنفيذ. النتيجة: الدالة بتتنفّذ بمعدّل أقصى قدره 1 / limit مرة في الميلي ثانية.
الفرق الرياضي باختصار: إذا وصل N استدعاء في فترة أقصر من wait، فإن debounce بتنفّذ الدالة مرة واحدة بالظبط، بينما throttle بتنفّذها floor(duration / limit) + 1 مرة.