Race Conditions در دریافت داده: بازگشت با جزئیات عملی

در فصل ششم با مفهوم Race Condition آشنا شدیم: وقتی پاسخ یک درخواست قدیمی، دیرتر از یک درخواست جدیدتر می‌رسد و اشتباهاً روی State اعمال می‌شود. در این درس، همان مشکل را مستقیماً در بافت دریافت داده مرور می‌کنیم و چند جزئیت عملی بیشتری اضافه می‌کنیم که در آن فصل فرصت پرداختن به آن‌ها نبود.

یادآوری سریع راه‌حل پایه

useEffect(() => {
  let ignore = false;

  fetchUser(userId).then((data) => {
    if (!ignore) {
      setUser(data);
    }
  });

  return () => {
    ignore = true;
  };
}, [userId]);

این همان الگویی است که در فصل ششم دیدیم: با هر تغییر userId، Cleanup اجرای قبلی، پرچم ignore را true می‌کند و جلوی اعمال نتیجه دیررسیده قدیمی را می‌گیرد.

چرا این مشکل در دریافت داده، به‌طور خاص رایج است

در بسیاری از Effectهای دیگر (مثل شنود رویداد resize)، زمان پاسخ قابل‌پیش‌بینی و تقریباً فوری است. اما درخواست‌های شبکه، به‌خاطر وابستگی به سرور و شبکه، می‌توانند زمان‌های کاملاً متفاوتی طول بکشند؛ یک درخواست سبک ممکن است زودتر از یک درخواست سنگین‌تر که قبلش ارسال شده، برگردد. به همین دلیل، تقریباً هر Effect دریافت داده که به یک مقدار متغیر (مثل id یا query) وابسته است، باید این محافظت را داشته باشد.

یک نسخه کامل‌تر: محافظت در برابر خطا هم

پرچم ignore باید هم مسیر موفقیت و هم مسیر خطا را پوشش دهد؛ وگرنه ممکن است خطای یک درخواست قدیمی و لغوشده، روی State فعلی ظاهر شود:

useEffect(() => {
  let ignore = false;
  setLoading(true);

  fetchUser(userId)
    .then((data) => {
      if (!ignore) setUser(data);
    })
    .catch((err) => {
      if (!ignore) setError(err.message);
    })
    .finally(() => {
      if (!ignore) setLoading(false);
    });

  return () => {
    ignore = true;
  };
}, [userId]);

توجه کنید که حتی setLoading(false) هم پشت همین شرط قرار گرفته؛ در غیر این صورت، یک درخواست لغوشده می‌تواند اشتباهاً وضعیت بارگذاری درخواست جدیدتر را خاموش کند.

این راه‌حل فقط جلوی اعمال نتیجه را می‌گیرد، نه ارسال درخواست را

یک نکته مهم این است که پرچم ignore درخواست شبکه‌ای که قبلاً ارسال شده را واقعاً متوقف نمی‌کند؛ درخواست همچنان در پس‌زمینه تمام می‌شود، فقط نتیجه‌اش نادیده گرفته می‌شود. برای مواردی که واقعاً می‌خواهیم خود درخواست لغو شود (مثلاً برای صرفه‌جویی در پهنای باند یا آزاد کردن منابع سرور)، به ابزار دیگری نیاز داریم که دقیقاً موضوع درس بعدی است: Abort و Cancellation.

مثال قابل اجرا

در این مثال، کاربر دوم عمداً سریع‌تر از کاربر اول پاسخ می‌دهد. به‌سرعت بین دو دکمه جابه‌جا شوید و ببینید نتیجه همیشه درست می‌ماند.

جلوگیری از Race Condition در دریافت داده
برای دیدن پیش‌نمایش کامپوننت، روی Run بزنید.

جمع‌بندی

در دریافت داده، به‌خاطر زمان‌بندی غیرقابل‌پیش‌بینی پاسخ‌های شبکه، Race Condition یک خطر رایج است. پرچم ignore در Cleanup باید هم مسیر موفقیت، هم خطا و هم خاموش کردن Loading را پوشش دهد. این راه‌حل، ارسال درخواست را متوقف نمی‌کند، فقط نتیجه آن را نادیده می‌گیرد؛ لغو واقعی درخواست، موضوع درس بعدی است.