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

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

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

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

المنصة

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

الدعم

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

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

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

Debounce و Throttle في JavaScript: الفرق اللي بيوفرلك 90% من طلبات الـ API

📅 ١٩ أبريل ٢٠٢٦⏱ 5 دقائق قراءة
Debounce و Throttle في JavaScript: الفرق اللي بيوفرلك 90% من طلبات الـ API
لو حقل بحث بيضرب الـ API مع كل ضغطة مفتاح، مستخدم بيكتب "javascript" هيعمل 10 طلبات في أقل من ثانيتين. Debounce و Throttle بيحلوا المشكلة دي، لكن القرار بين الاتنين بيغير سلوك التطبيق تمامًا.

شاشة محرر كود JavaScript تعرض دالة معالجة أحداث مع مؤقت setTimeout

Debounce vs Throttle: القرار بالظبط

المشكلة باختصار

أحداث زي input وscroll وresize بتتطلق بمعدل عالي جدًا. الـ scroll ممكن يطلق أكتر من 100 حدث في الثانية على متصفح عادي. لو ربطت بيه دالة تعمل حسابات أو ترسم عناصر DOM، الصفحة هتتلج. ولو ربطت بيه طلب API، هتحرق الـ rate limit في ثواني.

الحل مش إنك تمنع الحدث. الحل إنك تتحكم في معدل استدعاء الدالة المرتبطة بيه. هنا بييجي دور Debounce و Throttle.

Debounce: استنى لحد ما يهدا

Debounce بيقولك "نفذ الدالة بس لما يعدي وقت معين من غير ما يتكرر الحدث". لو المستخدم لسه بيكتب، الـ timer بيتعمل reset. الدالة بتشتغل مرة واحدة بعد ما يبطل.

الاستخدام الأشهر: حقل بحث يضرب API. مش منطقي تبعت طلب مع كل حرف. منطقي تستنى 300ms بعد آخر حرف، وبعدين تبعت طلب واحد بالنص الكامل.

JavaScript
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. ده سلوك غلط.

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

// الاستخدام الفعلي
const handleScroll = throttle(() => {
  const scrollPercent =
    (window.scrollY / (document.body.scrollHeight - window.innerHeight)) * 100;
  updateProgressBar(scrollPercent);
}, 100);

window.addEventListener("scroll", handleScroll);

النتيجة: بدلًا من 100+ استدعاء في الثانية، الدالة بتشتغل 10 مرات فقط (مرة كل 100ms). الـ UI بيفضل سلس والحسابات بتقل بنسبة ~90%.

الفرق اللي بيكسر الـ production

الناس بتستخدم debounce في مكان throttle والعكس. النتيجة: bug صعب تشخيصه.

  • Debounce: الدالة بتشتغل مرة واحدة بعد آخر حدث. لو الأحداث ما بطلتش، الدالة ما بتشتغلش أبدًا. مناسب: بحث، auto-save، validation بعد الكتابة.
  • Throttle: الدالة بتشتغل بانتظام أثناء الأحداث. لو الأحداث مستمرة، الدالة بتتنفذ كل X ms. مناسب: scroll، mousemove، resize، game loops.

مثال كارثي حصل في projects فعلية: استخدام debounce على زرار "إرسال" عشان تمنع double-click. المستخدم ضغط مرتين بسرعة، الدالة بتتأجل، الطلب بيتبعت بعد 300ms، المستخدم يفتكر إن الزرار بايظ ويضغط تاني. الحل الصح هنا هو throttle مع leading edge (ينفذ أول مرة فورًا، ويمنع التكرار لفترة).

trade-offs لازم تعرفها

كل واحد منهم معاه ثمن.

  • Debounce بيأخر استجابة المستخدم. 300ms بتحس بيها في البحث الحي. لو عايزها فورية استخدم 100–150ms مع إشارة loading.
  • Throttle بيضيع آخر حدث. لو المستخدم وقف scroll في نص فترة throttle، آخر موضع ممكن ميتسجلش. الحل: استخدم نسخة فيها trailing: true زي اللي في lodash.
  • الدالتين محتاجين cleanup. في React مع useEffect، لازم ترجع clearTimeout في الـ cleanup function، وإلا هتلاقي memory leaks في components بتتعمل unmount.

افتراض مهم: الأرقام (300ms, 100ms) مبنية على أن المستخدم بيتعامل مع الـ UI على جهاز عادي. لو التطبيق على mobile بشبكة 3G، ممكن تحتاج 500ms للـ debounce عشان تقلل الطلبات أكتر.

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

في حالات الحل فيها مش debounce ولا throttle:

  • طلب لازم يتبعت فورًا: زي دفع أو تأكيد عملية. هنا استخدم disable للزرار + loading state بدل ما تأجل الطلب.
  • الحدث بيحصل مرة واحدة فعلًا: زي submit أو click على زرار عادي. مفيش داعي لأي من الاتنين.
  • عندك Observable (RxJS): استخدم debounceTime أو throttleTime من الـ library مباشرة، بدل ما تكتب implementation يدوي.

جدول قرار سريع

  • بحث حي يضرب API → debounce 300ms
  • حفظ تلقائي لنص طويل → debounce 1000ms
  • scroll يحدث progress bar → throttle 100ms
  • mousemove لرسم في canvas → throttle 16ms (60fps)
  • resize يحدث layout → throttle 150ms
  • button anti-double-click → throttle 500ms leading

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

افتح devtools network tab في التطبيق بتاعك وفلتر على XHR. ابحث في حقل البحث واعد الطلبات. لو لقيت أكتر من طلب في الثانية، ضيف debounce 300ms على الـ handler. لو عندك scroll handler، شغل Performance tab في Chrome واتفرج على عدد استدعاءات الدالة في الثانية. أي رقم فوق 10 معناه إنك محتاج throttle. استخدم lodash.debounce وlodash.throttle مباشرة لو مش عايز تكتب implementation بنفسك — مجرّبين في production لآلاف المشاريع.

المصادر

  • MDN — setTimeout API documentation
  • Lodash — debounce documentation
  • Lodash — throttle documentation
  • CSS-Tricks — Debouncing and Throttling Explained
  • RxJS — debounceTime operator

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

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

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