Promise.all مقابل Promise.allSettled في JavaScript: اختار الصح ومتفقدش نص النتايج
لو dashboard بيستدعي 8 APIs بالتوازي بـ Promise.all، وفجأة الصفحة كلها بتختفي لما API واحد بس يفشل، انت بتدفع تكلفة قرار غلط في 4 سطور كود. Promise.allSettled في ECMAScript 2020 بيحل المشكلة دي بدون ما تخسر سرعة التوازي، بس فيه trade-offs لازم تعرفها قبل ما تغيّر.
المشكلة باختصار
الفرق بين الـ method-ين كلمتين: fail-fast مقابل wait-for-all. Promise.all أول ما يلاقي promise واحد رفض، بيرفض الـ Promise الكبير كله ويسيب الباقي. ده ممتاز لو محتاج كل النتايج علشان تكمل، بس كارثة لو نتايجك مستقلة عن بعض زي 8 widgets في dashboard. لو 7 منهم اشتغلوا والـ 8 رفض، انت خسرت السبعة كمان من غير سبب حقيقي.
المفهوم بمثال للمبتدئ
تخيّل عندك 5 مطاعم بتطلب منهم delivery في نفس الوقت لحفلة في البيت. في طريقتين تتعامل معاهم.
الطريقة الأولى (Promise.all): أول ما مطعم واحد يقولك "آسف الأكل خلص"، انت بترفض الـ 4 الباقيين حتى لو الأكل جاهز عندهم. النتيجة: حفلة فاشلة، الضيوف جعانين، وانت ادفعت 4 رسوم delivery على الفاضي.
الطريقة التانية (Promise.allSettled): انت بتستنى كل المطاعم يردّوا (سواء وافقوا أو رفضوا)، وبعدين بتقرر تاكل من اللي وافقوا وتلغي اللي رفضوا. النتيجة: 4 أطباق على الترابيزة، حفلة شغّالة.
الفرق التقني بالظبط هو ده. Promise.all بيـ short-circuit على أول رفض. Promise.allSettled بيستنى الكل ويرجع array من objects شكلها {status, value | reason} لكل promise.
التعريف التقني الدقيق
حسب ECMAScript 2020 Specification (TC39, Section 27.2.4.2)، Promise.allSettled بياخد iterable من promises وبيرجع promise واحد بيـ resolve لما كل العناصر تخلص بأي نتيجة. كل عنصر في الـ array الراجعة بيكون object فيه:
status: "fulfilled"+value: لما الـ promise نجح.status: "rejected"+reason: لما الـ promise رفض.
الافتراض هنا إن عندك Node.js 12.10+ أو Chrome 76+ أو Firefox 71+ (إصدارات بعد سبتمبر 2019). أي بيئة أقدم محتاجة polyfill من core-js أو es-shims/promise.allsettled.
الكود الفعلي
// dashboard.js — Node.js 22
async function loadDashboard(userId) {
const start = performance.now();
const results = await Promise.allSettled([
fetch(`/api/profile/${userId}`).then(r => r.json()),
fetch(`/api/notifications/${userId}`).then(r => r.json()),
fetch(`/api/recent-orders/${userId}`).then(r => r.json()),
fetch(`/api/recommendations/${userId}`).then(r => r.json()),
]);
const widgets = {};
const failures = [];
const keys = ['profile', 'notifications', 'orders', 'recs'];
results.forEach((res, i) => {
if (res.status === 'fulfilled') {
widgets[keys[i]] = res.value;
} else {
failures.push({ widget: keys[i], reason: res.reason.message });
}
});
if (failures.length > 0) {
// ابعتها لـ Sentry/DataDog، متخلهاش في الصمت
console.error('partial dashboard failure', failures);
}
console.log(`loaded in ${(performance.now() - start).toFixed(0)}ms`);
return { widgets, failures };
}