انتقل إلى محتوى الدرس
الدرس 39
دورة ReactJS

إلغاء الطلبات ومنع تضارب الاستجابات بـ AbortControllerCanceling Requests and Preventing Race Conditions

احمِ واجهة React من تضارب ردود الشبكة المتأخرة باستخدام راية التجاهل في دالة التنظيف وأداة AbortController القياسية وفصل خطأ الإلغاء المقصود وتهدئة البحث.
أقسام الدرس
6
الأمثلة
3
اللغة
العربية
تاريخ النشر
غير محدد
المشاهدات
0
محتوى الدرس
في هذا الدرس 6تصفّح المواضيع

لا تضمن الشبكات وصول ردود بروتوكول HTTP بنفس ترتيب إرسالها دائماً. تخيل أن المستخدم نقر على تبويب Frontend فاستغرق الخادم 500ms، ثم نقر فوراً على تبويب Hooks فوصل رده الخفيف خلال 120ms فقط. في غياب دالة تنظيف، ستظهر دورات Hooks أولاً، ثم يصل الرد القديم المتأخر بعد أجزاء من الثانية ويستدعي setCourses() فيطمس الشاشة ببيانات التبويب السابق بالخطأ!

يُعرف هذا الخلل الزمني الشهير باسم تضارب السباق في الشبكة (Race Condition)؛ لأن طلبين متوازيين يتسابقان لتحديث نفس الحالة دون مراعاة أيّهما يمثل اختيار المستخدم الأخير.

أبسط دفاع قياسي يوصي به التوثيق الرسمي لمكتبة React ضد تضارب الردود هو استخدام راية منطقية محلية داخل useEffect(): نعرّف let ignore = false في مطلع التأثير، ونشترط if (!ignore) قبل أي تحديث للحالة، ثم نعيد دالة التنظيف () => { ignore = true; }.

نظراً لأن كل دورة تصيير تمتلك نطاقاً مغلقاً خاصاً بها (Closure)، فإن تغيير التصنيف يضبط راية الطلب القديم على true بينما يبدأ الطلب الجديد براية جديدة false، فتُهمل أي نتيجة متأخرة بأمان تام.

HTML
useEffect(() => {
  let ignore = false;
  async function load() {
    const url = `/api/courses?category=${cat}`;
    const res = await fetch(url);
    const data = await res.json();
    if (!ignore) setCourses(data);
  }
  load();
  return () => { ignore = true; };
}, [cat]);

تحمي راية ignore حالة المكون من التلوث، لكن المتصفح يظل مستمراً في تنزيل بقية حزمة الشبكة المهملة في الخلفية. لإلغاء اتصال الشبكة الفعلي ذاته، أنشئ كائن new AbortController() القياسي داخل useEffect()، ومرّر الإشارة { signal: controller.signal } ضمن خيارات fetch()، ثم استدعِ controller.abort() داخل دالة التنظيف.

يوفر الإلغاء الفعلي للطلبات الملغاة استهلاك البيانات ويحرر قنوات الاتصال في المتصفح فوراً، وهو أمر بالغ الأهمية في القوائم الكبيرة والبحث الفوري.

مخطط يقارن بين مشكلة طمس الرد البطيء للبيانات الحديثة وبين الحماية القياسية بإلغاء الطلب عبر AbortController
HTML
useEffect(() => {
  const controller = new AbortController();
  fetch(url, { signal: controller.signal })
    .then((r) => r.json())
    .then(setCourses)
    .catch((e) => {
      if (e.name !== 'AbortError') setStatus('error');
    });
  return () => controller.abort();
}, [url]);

عندما تلغي الدالة controller.abort() طلب fetch() قيد التنفيذ، يرفض المتصفح الوعد باستثناء قياسي خاص يحمل الاسم err.name === 'AbortError'. فإذا تعاملت كتلة catch مع كافة الأخطاء بالطريقة نفسها، فسيظهر للمستخدم تنبيه فشل أحمر زائف بمجرد تنقله السريع بين التبويبات!

افحص دائماً الشرط if (err.name === 'AbortError') return; في أول سطر من catch. وإذا استخدمت كتلة finally لإيقاف مؤشر التحميل، فقيّدها بالشرط if (!controller.signal.aborted) حتى لا يوقف الطلب الملغى مؤشر التحميل الخاص بالطلب الأحدث الجاري حالياً.

تُطلق حقول البحث الفوري حدث التغيير مع كل حرف يكتبه المستخدم. يتيح دمج مؤقت انتظار قصير عبر setTimeout() (مثل 250ms) مع AbortController داخل دالة تنظيف واحدة تجربة بحث فائقة السلاسة دون إغراق الخادم بطلبات غير مكتملة:

  • إذا كتب المستخدم حرفاً تالياً قبل مرور 250ms، تلغي clearTimeout(timerId) الطلب المجدول قبل انطلاقه أصلاً.
  • وإذا كان هناك طلب سابق قد انطلق بالفعل، تتكفل controller.abort() بإلغائه فوراً.
HTML
useEffect(() => {
  const ctrl = new AbortController();
  const id = setTimeout(() => {
    const opts = { signal: ctrl.signal };
    fetch(`/api/courses?q=${q}`, opts)
      .then((r) => r.json())
      .then(setResults)
      .catch(() => {});
  }, 250);
  return () => { clearTimeout(id); ctrl.abort(); };
}, [q]);

إن الجمع بين مصفوفة الاعتماديات الدقيقة، وإلغاء الاتصال عبر AbortController، واستثناء AbortError المتوقع يقضي جذرياً على تلوث الحالة في التبويبات والفلاتر. يلخص الجدول التالي مستويات الحماية الأربعة:

أسلوب الجلبهل يمنع تلوث الحالة؟الأثر على استهلاك الشبكة
جلب بدون دالة تنظيفلا — الردود البطيئة تطمس التبويبات الأحدثيستكمل تنزيل جميع الردود المهملة بالكامل
راية التجاهل المنطقية ignoreنعم — يتخطى تحديث الحالة للطلب المتقادميستمر تنزيل الرد في الخلفية دون عرضه
أداة AbortController مع signalنعم — يوقف المسار القديم ويتجاهل نتيجتهيقطع طلب الشبكة غير اللازم فوراً
مؤقت تأجيل + AbortControllerنعم — يحصّن حقول البحث السريعة المتلاحقةيختصر الأحرف البينية ويلغي الطلبات البطيئة
خلاصة المعرفة ومحطة الانتقال

ملخص الدرس

حصاد الدرس

مراجعة مركّزة لأهم المفاهيم والقواعد الهندسية التي أتقنتها في هذا الدرس قبل التطبيق العملي

ماذا تعلّمت في هذا الدرس؟

تعرفت في هذا الدرس على أسباب تضارب ردود الشبكة المتأخرة في React وكيفية القضاء عليها عبر راية التجاهل في دالة التنظيف، وإلغاء الطلبات بـ AbortController، واستثناء AbortError المتوقع، وتهدئة طلبات البحث المتلاحقة. الخطوة التالية: انتقل الآن إلى الدرس رقم 40 «إرسال وتحديث وحذف البيانات عبر طلبات HTTP في React» لمواصلة البناء التراكمي المتدرج في مسار React الحديث.

مشاركة

هل كان هذا الدرس مفيدًا؟

جارٍ تحميل الآراء…

ابنِ لغتك البرمجية

المصطلحات4

راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.

0 / 4 مفهومةأول بطاقة، أول خطوة
01 Race Condition تضارب السباق الزمني
خلل ينشأ عندما يصل رد طلب قديم بطيء بعد اكتمال طلب أحدث منه فيطمس البيانات الصحيحة في الواجهة.
للمراجعة
02 AbortController متحكم إلغاء الطلبات
أداة متصفح قياسية تولّد إشارة AbortSignal قادرة على قطع طلبات fetch الجارية فوراً عند انتفاء الحاجة إليها.
للمراجعة
03 AbortError استثناء الإلغاء المقصود
اسم الاستثناء الذي يرميه المتصفح عند إلغاء الطلب عمداً، ويجب تجاهله في catch بدلاً من عرضه كعطل.
للمراجعة
04 Closure Flag راية النطاق المغلق
متغير منطقي محلي خاص بدورة تشغيل واحدة للتأثير يتحول إلى true عند التنظيف لمنع تحديث الحالة المتأخر.
للمراجعة

اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.

حروف قليلة. معنى كامل.

الاختصارات4

راجع المعنى، اشرحه لنفسك، ثم علّم البطاقة كمفهومة.

0 / 4 مفهومةأول بطاقة، أول خطوة
01 FIFO FIFO
First In, First Out
ترتيب الوصول المتسلسل الذي لا تضمنه الشبكات عند إرسال عدة طلبات غير متزامنة متتالية.
للمراجعة
02 DOM DOM
Document Object Model
مواصفة المتصفح القياسية التي تعرّف كائنات الصفحة والاستثناءات القياسية مثل DOMException.
للمراجعة
03 HTTP HTTP
Hypertext Transfer Protocol
بروتوكول نقل البيانات الذي يتم قطع اتصاله الجاري فور استدعاء دالة controller.abort().
للمراجعة
04 UX UX
User Experience
تجربة المستخدم التي تبقى متسقة وخالية من ومضات التحميل أو تداخل بيانات التبويبات.
للمراجعة

اقرأ البطاقات بحرية. سجّل الدخول لحفظ تقدم مراجعتك.

من الفهم للتطبيق

مهامك قبل الاختبار

خطوة صغيرة كل مرة. وإنجاز تقدر ترجع له.

0/2مهام منجزة
  1. افتح مثال SafeCategoryTabs في محرر التجريب وانقر frontend ثم hooks بسرعة وتأكد من بقاء دورات hooks الصحيحة على الشاشة.

  2. افتح مثال AbortableCourseViewer في محرر التجريب وبدّل بين الأزرار بسرعة لملاحظة ارتفاع عداد الطلبات الملغاة بنظافة.

تأشير المهمة إقرار منك بإتمامها، وليس تصحيحًا آليًا للكود. يُحفظ تقدمك في حسابك.

مساحة التحدي

اختبر فهمك.
ووصلها للإتقان.

كل سؤال خطوة لقدّام. اختار، اكتب كود، وشوف نتيجتك فورًا.

10أسئلة 2أنواع تحديات 100%هدف الإتقان
تصحيح تلقائي. فرص بلا حدود.

أكمل المهام، واجمع كل الإجابات الصحيحة. أقل من 100%؟ راجع وأعد المحاولة.

الأسئلة متاحة للاطلاع. سجّل الدخول للتفاعل وحفظ تقدمك.

لمحة عن التحدي10 أسئلة تنتظرك
01

كيف يفسد تضارب السباق الزمني واجهة مكون React عند التبديل السريع بين التبويبات؟

يصل رد الطلب الأقدم البطيء متأخراً بعد اكتمال الطلب الأحدث فيستبدل الحالة ببيانات قديمة خاطئة.

02

تعريف let ignore = false داخل التأثير وضبطها على true في دالة التنظيف يمنع الردود المتقادمة من تحديث الحالة.

صواب؛ لأن كل دورة تأثير تحتفظ بمتغير ignore مستقل في نطاقها المغلق.

03

كيف تربط كائن AbortController بطلب الشبكة القياسي fetch()؟

بتمرير الكائن { signal: controller.signal } في المعامل الثاني لدالة fetch.

04

أين يجب استدعاء controller.abort() عند جلب البيانات داخل خطاف useEffect؟

داخل دالة التنظيف المُعادة من useEffect لتعمل فور تغير المدخلات أو إزالة المكون.

05

يجب إنشاء كائن new AbortController() جديد في كل تشغيل لـ useEffect لأن الإشارة الملغاة تبقى ملغاة دائماً.

صواب؛ فإعادة استخدام إشارة ملغاة ترفض أي طلب جديد على الفور.

06

ما قيمة الخاصية err.name للاستثناء الناتج عن إلغاء طلب fetch بواسطة controller.abort()؟

يرفض المتصفح الطلب الملغى باستثناء قياسي يحمل الاسم النصي "AbortError".

07

لماذا يجب الخروج بـ return دون تفعيل حالة الخطأ عندما يكون err.name مساوياً لـ "AbortError"؟

لأن الإلغاء تم عمداً لتنظيف طلب قديم وليس بسبب عطل حقيقي في الشبكة.

08

حماية إيقاف التحميل داخل finally بالشرط !controller.signal.aborted تمنع الطلب الملغى من إخفاء مؤشر التحميل للطلب الجديد.

صواب؛ لأن finally تعمل حتى عند الإلغاء، فلا نعدل الحالة إلا إذا لم تكن الإشارة ملغاة.

09

عند دمج مؤقت setTimeout مع AbortController للبحث الحي، ماذا يجب أن تنفذ دالة التنظيف؟

تستدعي كلاً من clearTimeout لإلغاء المؤقت المنتظر وcontroller.abort() لإلغاء أي طلب جارٍ.

10

ما الميزة الإضافية التي تقدمها أداة AbortController مقارنة براية التجاهل المنطقية ignore وحدها؟

تقطع اتصال الشبكة الفعلي في المتصفح فتصون استهلاك البيانات بدلاً من الاكتفاء بإهمال النتيجة بعد تنزيلها.