أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

المنصة مبنية على الوضوح، التطبيق، والنتيجة النافعة: شرح مرتب يساعدك تفهم الأدوات، تكتب كودًا أفضل، وتستخدم الذكاء الاصطناعي بوعي داخل العمل الحقيقي.

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول

Debounce vs Throttle بالعربي: وفّر 95% من طلبات الـ API في الـ search box

📅 ١٩ أبريل ٢٠٢٦⏱ 6 دقائق قراءة
Debounce vs Throttle بالعربي: وفّر 95% من طلبات الـ API في الـ search box

لو الـ 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.

شاشة كود JavaScript تعرض دوال event handler لحقل بحث

الفكرة بمثال بسيط: المصعد والمدخّن

قبل التعريف العلمي، ركّز على مشهدين من حياتك العادية:

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 مرة.

تنفيذ من الصفر في JavaScript

ممنوع تستورد Lodash كاملة علشان دالة 10 أسطر. اكتبها بنفسك وافهم اللي بيحصل:

JavaScript
function debounce(fn, wait) {
  let timer = null;
  return function (...args) {
    if (timer) clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), wait);
  };
}

function throttle(fn, limit) {
  let lastCall = 0;
  return function (...args) {
    const now = Date.now();
    if (now - lastCall >= limit) {
      lastCall = now;
      fn.apply(this, args);
    }
  };
}

الاستخدام العملي في الواجهة:

JavaScript
const input = document.querySelector('#search');

// طلب واحد بعد ما المستخدم يسكت 300ms
input.addEventListener(
  'input',
  debounce((e) => fetchResults(e.target.value), 300)
);

// الـ handler يشتغل كل 100ms كحد أقصى خلال الـ scroll
window.addEventListener(
  'scroll',
  throttle(() => updateHeader(), 100)
);

امتى debounce وامتى throttle

القاعدة: لو المهم هو القيمة النهائية بعد ما تهدى الأحداث → debounce. لو المهم هو التحديث المستمر بمعدّل يقدر الجهاز عليه → throttle.

  • Search autocomplete: debounce بـ 300ms. محتاج نتائج الكلمة اللي خلّصها المستخدم، مش نتائج كل حرف وسطها.
  • Resize window: debounce بـ 200ms. عايز تعيد تخطيط الصفحة مرة واحدة بعد ما يخلّص التحريك، مش 200 مرة وسطه.
  • Scroll position (shrinking header، lazy load، infinite scroll): throttle بـ 100ms. عايز التحديث يحصل باستمرار بس مش 60 مرة/ثانية.
  • Mouse move على canvas رسم: throttle بـ 16ms (≈ 60fps). فقدان بعض الحركات مقبول، توقّف كامل لأ.
  • Form validation أثناء الكتابة: debounce بـ 400ms. بتبان رسالة خطأ واحدة بعد ما المستخدم يستريح.
  • Button submit: لا debounce ولا throttle. استخدم disabled state بعد أول ضغطة.

قياس فعلي على search box

الإعداد: مستخدم كتب جملة 20 حرف بمعدّل 5 ضغطات/ثانية، زمن الـ API 280ms في المتوسط.

  • بدون حماية: 20 طلب API، مدة إجمالية 5.6 ثانية، تكلفة AWS API Gateway ≈ $0.00007 للطلب الواحد × 20 = $0.0014.
  • Debounce 300ms: طلب واحد بعد ما يقف، زمن استجابة 320ms، توفير 95% في عدد الطلبات.
  • Throttle 400ms: 5 طلبات، تجربة مستخدم أحسن شوية لأن النتائج بتيجي وهو بيكتب، بس زيادة 5× في الطلبات مقارنة بـ debounce.

ملاحظة: الأرقام مبنية على فرضية إن الـ API latency ≤ 400ms. لو الـ latency أعلى، debounce بيبقى إجباري لأن throttle هيكدّس طلبات معلّقة بتنفّذ بعد ما خلص المستخدم يكتب.

شخص يكتب استعلام بحث على لوحة مفاتيح لابتوب مع اقتراحات تظهر مباشرة

Trade-offs لازم تعرفها قبل ما تكتب سطر

Debounce: بتكسب توفير هائل في الطلبات وإلغاء الـ race conditions. بتخسر responsiveness — المستخدم بيحس إن الـ UI ساكت لحد ما يخلّص. الحل: اعرض spinner فورًا مع أول ضغطة، ده بيشتري صبر 300ms بسهولة.

Throttle: بتكسب تحديث سلس ومستمر. بتخسر دقة آخر قيمة — لو الحدث الأخير وصل في آخر 50ms قبل انتهاء الـ limit، بيتلغى نهائيًا. الحل: implementation مع trailing edge يضمن التنفيذ في آخر الفترة، زي اللي في Lodash بخيار { trailing: true }.

الافتراض في كل الأرقام فوق: متصفح حديث، زمن API ≤ 400ms، ومعدّل كتابة ≤ 7 حروف/ثانية. لو عندك WebSocket real-time أو معدّل أعلى، القيم بتتغيّر وتحتاج تقيس بنفسك.

متى لا تستخدم أي منهما

لو الحدث بيحصل مرة واحدة في كل تفاعل (زي click على submit)، مفيش حاجة اسمها debounce لزرار — المشكلة دي بتتحل بـ button.disabled = true بعد أول ضغطة.

لو بتتعامل مع WebSocket بيبعث رسائل real-time مهمّة (ألعاب متعددة اللاعبين، تطبيقات trading)، debounce بيحذف معلومات فعلية عن حالة السيستم. استخدم batching بـ requestAnimationFrame أو micro-batching بـ queue زمني بدلًا منه.

لو بتكتب React، ما تعملش debounce على function جوا المكوّن بدون useCallback أو useMemo. هتخلق debounced function جديدة كل render، وكل استدعاء هيكون على timer مستقل. استخدم useMemo(() => debounce(fn, 300), []) أو hook جاهز زي useDebouncedCallback من مكتبة use-debounce.

الخطوة التالية

افتح الـ search box في مشروعك الحالي. افتح DevTools ← تبويب Network. اكتب كلمة 8 حروف. لو شفت 8 طلبات شبكة، عندك المشكلة دي بالظبط. ضيف debounce بـ 300ms بالكود اللي فوق، جرّب تاني، ولازم تشوف طلب واحد بس. ده توفير فوري 87% من الحمل، بدون أي تأثير سلبي على تجربة المستخدم.

المصادر

  • MDN: setTimeout و clearTimeout
  • Lodash docs: debounce options (leading / trailing / maxWait)
  • Lodash docs: throttle
  • CSS-Tricks: Debouncing and Throttling Explained
  • MDN: requestAnimationFrame كبديل للـ throttle البصري
  • npm: use-debounce (React hook موثّق)

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة