المستوى: متوسط — هذا المقال يفترض إنك تعرف Promises الأساسية و async/await و عملت قبل كده طلبات API متوازية بـ Promise.all. لو لسه مبتدئ تماماً، ابدأ بمقال Event Loop الأول.
لو dashboard بتاعك بيرجع شاشة فاضية لأن طلب واحد من 8 طلبات API فشل، المشكلة مش في الـ network ولا في الـ backend. المشكلة في سطر واحد: انت بتستخدم Promise.all في حالة المفروض فيها Promise.allSettled.
ليه dashboard كامل بيختفي بسبب widget واحد فاشل
قبل ما ندخل في الكود، تخيل المشهد ده:
انت في فرع بنك. عندك 8 شبابيك مفتوحة لـ 8 عملاء في نفس الوقت. شباك واحد بس وقع موظفه على الأرض من التعب. باقي السبعة شغالين تمام، والعملاء لسه بياخدوا خدمة. مدير البنك جه شاف موظف واقع، قرر إنه يقفل الفرع كله ويرجع كل الناس البيت. السبعة عملاء اللي كانوا بياخدوا خدمة فعلاً ضاعت معاملاتهم.
ده بالظبط اللي بيحصل لما Promise.all يلاقي promise واحد فشل وسط 8 promises شغالين تمام.
المشكلة باختصار
Promise.all بتشتغل بقاعدة fail-fast: أول promise يدخل state rejected، الـ aggregator promise يدخل rejected هو كمان فوراً. أي نتايج وصلت من الـ promises التانية بتتجاهل تماماً. النتيجة العملية: dashboard كامل بـ 8 widgets بيختفي عشان widget واحد بس فشل تحميله.
السيناريو ده شائع جداً في تطبيقات الـ admin والـ analytics اللي بتجمع بيانات من مصادر متعددة. endpoint واحد بطيء أو بيرجع 503 لـ 5% من الطلبات يكفي يخلي تجربة المستخدم تبوظ.
الفرق بين الاتنين علميا
الاتنين combinators على الـ Promise prototype في ECMAScript، لكن سلوكهم مختلف جذرياً:
Promise.all(iterable)— موجودة من ES2015. بترجع promise بيـ resolve لما كل promises الـ iterable تـ fulfill، أو بيـ reject فوراً عند أول rejection. النتيجة array بنفس ترتيب الـ inputs.Promise.allSettled(iterable)— انضمت لـ ECMAScript 2020 (proposal Stage 4 سنة 2019). بتنتظر كل الـ promises تخلص بغض النظر عن النتيجة. بترجع array من objects، كل object فيهstatusو إماvalueأوreason.
الفرق الجوهري في الـ semantics: Promise.all بتعامل أي فشل كأنه catastrophic. Promise.allSettled بتعامل الفشل كحالة عادية ضمن النتايج، وبتسيب لك انت تقرر إيه اللي يحصل بعدها.
كود يعيد إنتاج المشكلة
السيناريو: dashboard بيحتاج 4 endpoints مختلفة عشان يعرض الواجهة كاملة.