المقدمة و history سريع كدا
المشكلة الأساسية (قبل 2010) 😫
تخيل معايا الموقف ده… إنت بتعمل موقع وعايز:
- تجيب بيانات المستخدم
- تجيب منشوراته
- تجيب الكومنتات بتاعته
- تعرض كل ده في الصفحة
الكود كان بيبقى شكله كده (Callback Hell) أو أحيانًا بـ يسموها Pyramid of Doom
getUserData(userId, function(user) {
getUserPosts(user.id, function(posts) {
getComments(posts[0].id, function(comments) {
getCommentsLikes(comments[0].id, function(likes) {
// 😱 يا نهار ! الكود بقى صعب يتقرأ
// وكمان لو حصل error في أي حتة، هنعرف منين؟
});
});
});
});
البداية: مشكلة فعلية في (Yahoo) 🎯
في 2007، مهندسين في Yahoo كانوا بيعانوا من مشكلة:
- كانوا محتاجين يحملوا صور كتير
- محتاجين يعرفوا امتى كل صورة خلصت تحميل
- محتاجين يتعاملوا مع الـ errors
- الكود كان بيبقى معقد جداً
كانوا بيكتبوا حاجة زي كده:
image1.onload = function() {
image2.onload = function() {
image3.onload = function() {
// 😅 تخيل لو عندك 100 صورة!
}
}
}
أو قريب من كده حسب القصص وتقارير تقنية عن مشاكل callback hell وقتها
function loadImage(url, callback) {
const img = new Image();
img.src = url;
img.onload = function () {
callback(null, img); // الصورة اتحملت بنجاح
};
img.onerror = function () {
callback(new Error(`Failed to load image from ${url}`)); // لو حصل خطأ
};
}
// تحميل الصور باستخدام callbacks
loadImage('image1.jpg', function (err, img1) {
if (err) {
console.error(err);
return;
}
loadImage('image2.jpg', function (err, img2) {
if (err) {
console.error(err);
return;
}
loadImage('image3.jpg', function (err, img3) {
if (err) {
console.error(err);
return;
}
console.log('All images loaded successfully!', img1, img2, img3);
});
});
});
ظهور أول فكرة للـ Promises (2010) 💡
CommonJS Promises/A
- مجموعة من المطورين قعدوا مع بعض
- قالوا: “لازم نلاقي حل للفوضى دي”
- فكروا في pattern يحل المشكلة
- سموه “Promises” لأنه زي “وعد” بإن حاجة هتحصل في المستقبل
الفكرة الأساسية كانت:
// بدل ما نحط callback جوا callback جوا callback
// نعمل سلسلة واضحة من الخطوات
getUser()
.then(getPosts)
.then(getComments)
.then(showResults);
التطور: jQuery Deferred (2011) 🚀
- jQuery عملت تطبيق للفكرة
- كان أول implementation شعبي
- الناس حبته لأنه سهل الاستخدام
$.ajax('/api/user')
.done(function(user) { /* نجح */ })
.fail(function(error) { /* فشل */ });
المشكلة مع jQuery Deferred 😕
- مكانش متوافق مع المعايير
- كان فيه bugs في التعامل مع errors
- مكانش بيشتغل مع كل أنواع الـ async operations
Promises/A+ (2012): المعيار الجديد 📋
- مجموعة من المطورين اجتمعوا
- حددوا معايير دقيقة لـ Promises
- عملوا specification كامل
- ده اللي بنستخدمه النهاردة
ES6 Promises (2015): الحل الرسمي 🎉
JavaScript أضافت الـ Promises رسمياً للغة:
// الشكل النهائي اللي بنستخدمه النهاردة
fetch('/api/user')
.then(response => response.json())
.then(user => console.log(user))
.catch(error => console.error(error));
Async/Await (2017): الشوجر سينتاكس 🍯
ES2017 أضاف syntax أحلى للتعامل مع الـ Promises:
async function getUserData() {
try {
const user = await getUser();
const posts = await getPosts(user.id);
const comments = await getComments(posts[0].id);
return comments;
} catch (error) {
console.error('حصل مشكلة:', error);
}
}
والأحلى من كده، الـ Promises فتحت الباب لتطورات تانية زي:
- async/await
- Promise.allSettled()
- Top-level await
مقارنة بصرية بين Callbacks و Promises
إيه هي الـ Promises أصلاً؟ 🤔
الـ Promise في JavaScript زي “وعد” أو “تعهد” إن حاجة معينة هتحصل في المستقبل.
تخيل إنك في مطعم وطلبت بيتزا:
لما بتطلب الأوردر:
- الويتر بيديك رقم طلب (ده الـ Promise)
- الطلب دخل المطبخ (Pending)
لو البيتزا اتعملت بنجاح:
- الويتر يجيب لك البيتزا (resolve)
- وانت كده حصلت على اللي طلبته (fulfilled)
لو خلصت المكونات أو حصلت مشكلة:
- الويتر يرجع يقولك في مشكلة (reject)
- وكده الطلب اتعمله reject (rejected)
شرح تفصيلي للكود 🔍
const orderFood = new Promise((resolve, reject) => {
// 1️⃣ دي function بتاخد parameter واحد أو اتنين
// resolve: function بتتنفذ لما كل حاجة تمام
// reject: function بتتنفذ لو في مشكلة
setTimeout(() => {
const success = true;
if (success) {
// 2️⃣ resolve بتنهي الـ Promise بنجاح
resolve("🍕 البيتزا وصلت!");
} else {
// 3️⃣ reject بتنهي الـ Promise بفشل
reject("😢 في مشكلة في الطلب");
}
}, 2000);
});
// 4️⃣ استخدام الـ Promise
orderFood
.then((result) => {
// resolve بيروح لـ then
console.log(result); // "🍕 البيتزا وصلت!"
})
.catch((error) => {
// reject بيروح لـ catch
console.log(error); // "😢 في مشكلة في الطلب"
});
تفاصيل أكتر عن resolve
resolve هي function:
// resolve دي function جاهزة بتاخد parameter واحد resolve(value); // ممكن تبعت أي value
ممكن تبعت أي نوع من البيانات:
resolve("Hello"); // string resolve(42); // number resolve({id: 1}); // object resolve([1, 2, 3]); // array
بتنقل البيانات لـ then:
new Promise((resolve) => { resolve("مرحبا"); }) .then((msg) => { console.log(msg); // "مرحبا" });
تفاصيل أكتر عن reject
تخيل كده reject زي “زر الإلغاء” أو “إشارة الخطأ” 🚫 لما بتعمل reject معناها إن في حاجة غلط حصلت وعايز تقول للكود “قف، في مشكلة!”
const checkAge = new Promise((resolve, reject) => {
const age = 15;
if (age >= 18) {
resolve("مسموح بالدخول"); // كله تمام
} else {
reject("السن صغير، مينفعش تدخل"); // 🚫 في مشكلة
}
});
// لما تستخدم الكود
checkAge
.then(msg => console.log(msg)) // هيتنفذ لو resolve حصل
.catch(error => console.log(error)) // هيتنفذ لو reject حصل
أي reject بيروح مباشرة للـ catch عشان تتعامل مع المشكلة. هو ببساطة طريقتك للقول “لا، في مشكلة” في الـ Promise.
ليه بنستخدم Promises؟ 🎯
عشان نتعامل مع الـ Asynchronous Operations
- لما بنجيب داتا من API
- لما بنقرا ملفات
- لما بنعمل عمليات بتاخد وقت
عشان نتجنب الـ Callback Hell
// الشكل القديم (Callback Hell)
doSomething(function(result) {
doSomethingElse(result, function(newResult) {
doThirdThing(newResult, function(finalResult) {
console.log('Got the final result: ' + finalResult);
}, failureCallback);
}, failureCallback);
}, failureCallback);
// الشكل الجديد باستخدام Promises
doSomething()
.then(result => doSomethingElse(result))
.then(newResult => doThirdThing(newResult))
.then(finalResult => {
console.log('Got the final result: ' + finalResult);
})
.catch(failureCallback);
الـ Methods الأساسية 🛠️
1. then()
الـ then بتاخد callback function كـ argument. الـ callback دي بتستلم قيمة النجاح (resolved value) بتاعة الـ Promise.
result: هو القيمة اللي الـ Promise رجعتها لما اتحلت بنجاح (resolved). لو الـ Promise رجّعت object، array، أو قيمة primitive، الـ result هتستقبلها.
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("الشغل خلص!"), 2000);
});
promise.then(result => {
console.log("النتيجة: " + result);
});
الـ resolve هو الطريقة اللي بنقول بيها للـ Promise “خلاص يا معلم، كله تمام وعندي النتيجة أهي”. وأي resolve بيروح للـ then اللي بعده.
ده زي ما تقول “طيب، لما تخلص اعمل كذا”. يعني لما الـ promise ينجح، هينفذ الكود اللي جوه الـ then.
لو عندك أكتر من then، ممكن الخطأ يظهر من أي واحدة فيهم، والـ catch هيستقبل أي خطأ سواء من الـ Promise الأصلي أو من أي كود في الـ then اللي قبله.
لو رجعت قيمة من داخل الـ then، القيمة دي هتبقى الـ resolved value للـ Promise الجديدة اللي بتنتج عن الـChaining Promises
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve(10), 1000);
});
promise
.then(result => {
console.log("النتيجة الأولى: " + result);
return result * 2;
})
.then(result => {
console.log("النتيجة الثانية: " + result);
return result * 3;
})
.then(result => {
console.log("النتيجة النهائية: " + result);
})
.catch(error => {
console.error("حصل error: " + error);
})
.finally(() => {
console.log("خلصنا السلسلة.");
});
2. catch()
الـ catch بتاخد callback function كـ argument. الـ callback دي بتستلم قيمة الخطأ (rejected reason) اللي حصل في الـ Promise أو في أي واحدة من الخطوات اللي قبلها.
error: هو السبب اللي الـ Promise اتحولت لـ rejected عشانه. ده ممكن يكون string، object، أو حتى error instance (لو استعملت throw new Error).
let promise = new Promise((resolve, reject) => {
reject("حصلت مشكلة أثناء التنفيذ!");
});
promise.catch(error => {
console.error("الخطأ: " + error); // "الخطأ: حصلت مشكلة أثناء التنفيذ!"
});
الـ catch بيتعامل مع الأخطاء لو حصلت أثناء تنفيذ الـ Promise، يعني لو الـ Promise اتحولت لـ rejected. الكود اللي جوه الـ catch بيتنفذ لما يحصل خطأ.
3. finally()
الـ finally هي المكان اللي بتكتب فيه حاجة عايزها تحصل سواء الـ Promise اتحلت بنجاح (resolve) أو فشلت (reject).
promise
.finally(() => {
console.log("ده هيتنفذ في كل الحالات");
});
- الـ finally مفيدة لو عندك تنظيف موارد (resource cleanup) زي إغلاق connections أو إخفاء loading spinner
- أي حاجة تكتبها في الـ finally مش هتأثر على القيمة الأصلية للـ Promise، يعني لو اتحلت أو فشلت، النتيجة النهائية تفضل زي ما هي.
أمثلة عملية 💡
1. جلب بيانات من API
function getUserData(userId) {
return fetch(`https://api.example.com/users/${userId}`)
.then(response => {
if (!response.ok) {
throw new Error('مفيش داتا');
}
return response.json();
})
.then(data => {
console.log('بيانات المستخدم:', data);
return data;
})
.catch(error => {
console.error('حصل error:', error);
throw error;
});
}
2. تحميل صورة
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject(new Error('مش قادر أحمل الصورة'));
img.src = url;
});
}
المشاكل الشائعة وحلولها 🔧
1. نسيان الـ Error Handling
// ❌ غلط
promise.then(result => {
// handle success
});
// ✅ صح
promise
.then(result => {
// handle success
})
.catch(error => {
// handle error
});
2. Promise Hell
// ❌ غلط
firstPromise()
.then(result1 => {
secondPromise(result1)
.then(result2 => {
thirdPromise(result2)
.then(result3 => {
console.log(result3);
});
});
});
// ✅ صح
async function betterWay() {
try {
const result1 = await firstPromise();
const result2 = await secondPromise(result1);
const result3 = await thirdPromise(result2);
console.log(result3);
} catch (error) {
console.error(error);
}
}
إزاي عملوا Implementation للـ Promise
تعالى نفهم الـPromises من جوا! هشرح ازاي ممكن نعمل implementation بسيط:
class MyPromise {
constructor(executor) {
// الحالات الأساسية للـpromise
this.PENDING = 'pending';
this.FULFILLED = 'fulfilled';
this.REJECTED = 'rejected';
// الحالة الابتدائية
this.state = this.PENDING;
this.value = undefined;
this.reason = undefined;
// arrays للـcallbacks
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
// بنعرف الـresolve
const resolve = (value) => {
if (this.state === this.PENDING) {
this.state = this.FULFILLED;
this.value = value;
// بننفذ كل الـcallbacks المسجلة
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
// بنعرف الـreject
const reject = (reason) => {
if (this.state === this.PENDING) {
this.state = this.REJECTED;
this.reason = reason;
// بننفذ كل الـcallbacks المسجلة
this.onRejectedCallbacks.forEach(callback => callback());
}
};
// بننفذ الـexecutor وبنهندل الـerrors
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
// الـthen method
then(onFulfilled, onRejected) {
// بنعمل promise جديد (عشان الـchaining)
return new MyPromise((resolve, reject) => {
// في حالة الـfulfilled
if (this.state === this.FULFILLED) {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
}
// في حالة الـrejected
else if (this.state === this.REJECTED) {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
}
// في حالة الـpending
else {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
});
}
});
}
// الـcatch method
catch(onRejected) {
return this.then(null, onRejected);
}
// الـfinally method
finally(callback) {
return this.then(
value => {
callback();
return value;
},
reason => {
callback();
throw reason;
}
);
}
}
هشرح الكود ده جزء جزء
class MyPromise {
constructor(executor) {
// بنعرف الثوابت بتاعت الحالات الممكنة للpromise
this.PENDING = 'pending'; // لما يكون شغال
this.FULFILLED = 'fulfilled'; // لما يخلص بنجاح
this.REJECTED = 'rejected'; // لما يفشل
// الحالة الابتدائية دايماً pending والقيمة undefined
this.state = this.PENDING;
this.value = undefined;
this.reason = undefined;
// هنا بنخزن الcallbacks اللي هتتنفذ بعدين
this.onFulfilledCallbacks = []; // للنجاح
this.onRejectedCallbacks = []; // للفشل
الجزء ده بيجهز الـstate management بتاع الـPromise. يعني هو بيعرف الحالات الممكنة والقيم الابتدائية.
// function الـresolve - بتتنفذ لما العملية تنجح
const resolve = (value) => {
if (this.state === this.PENDING) {
this.state = this.FULFILLED;
this.value = value;
// بننفذ كل الcallbacks المحفوظة
this.onFulfilledCallbacks.forEach(callback => callback());
}
};
// function الـreject - بتتنفذ لما يحصل error
const reject = (reason) => {
if (this.state === this.PENDING) {
this.state = this.REJECTED;
this.reason = reason;
// بننفذ كل الcallbacks بتاعت الerror
this.onRejectedCallbacks.forEach(callback => callback());
}
};
ده الجزء المسؤول عن تغيير حالة الـPromise. لما تنجح العملية بيتنفذ resolve ولما تفشل بيتنفذ reject.
// بننفذ الكود اللي جوا الpromise
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
هنا بننفذ الكود اللي المبرمج حطه جوا الـPromise، ولو حصل error بنحوله لـreject.
// الـthen method بتاخد callback للنجاح وcallback للفشل
then(onFulfilled, onRejected) {
return new MyPromise((resolve, reject) => {
// لو الpromise خلص
if (this.state === this.FULFILLED) {
setTimeout(() => {
try {
const result = onFulfilled(this.value);
resolve(result);
} catch (error) {
reject(error);
}
}, 0);
}
ده الـthen method اللي بتسمح بالـchaining. بترجع promise جديد وبتنفذ الـcallback المناسب حسب حالة الـpromise.
// الـcatch بتاخد callback للerrors بس
catch(onRejected) {
return this.then(null, onRejected);
}
// الـfinally بتتنفذ في كل الحالات
finally(callback) {
return this.then(
value => {
callback();
return value;
},
reason => {
callback();
throw reason;
}
);
}
النقاط المهمة في التنفيذ:
الـState Machine:
- كل Promise ليه 3 حالات:
pending
,fulfilled
,rejected
. - بيبدأ بـ
pending
وبيتغير مرة واحدة بس، سواء لـfulfilled
أوrejected
.
- كل Promise ليه 3 حالات:
الـMicrotask Queue:
- استخدمنا
setTimeout
علشان نحاكي الـ microtask queue. - في التنفيذ الحقيقي بيستخدموا
queueMicrotask
علشان يضمنوا تنفيذ المهام بعد الـ synchronous code مباشرة.
- استخدمنا
الـChaining:
- كل
then
بيرجع Promise جديد. - ده بيسمح بالـ method chaining، يعني ممكن تكتب كود متسلسل بسهولة.
- كل
الـError Handling:
- أي error في الـ
executor
بيتحول لـrejection
. - الـ
catch
method بتسهل الـ error handling وتساعد في التعامل مع الأخطاء بشكل منظم.
- أي error في الـ
الـCallback Storage:
- بنخزن الـ callbacks في arrays لو الـ Promise لسه
pending
. - بننفذهم لما الـ state يتغير من
pending
إلىfulfilled
أوrejected
.
- بنخزن الـ callbacks في arrays لو الـ Promise لسه
الـImmutability:
- بمجرد ما الـ state يتغير مش بيتغير تاني.
- ده بيضمن الـ predictability للكود، يعني مفيش تغيير مفاجئ في الحالات بعد ما تكون اتحددت.
الـFinally Handler:
- بيتنفذ في كل الحالات (سواء الـ Promise fulfilled أو rejected).
- بيحافظ على الـ
value
أوreason
ويضمن إنه ينفذ سواء كان فيه نجاح أو فشل.
التنفيذ ده بسيط نسبياً مقارنة بالتنفيذ الحقيقي في المتصفحات، لكنه بيوضح الـ core concepts زي:
- State management
- Asynchronous execution
- Error handling
- Chaining
- Event queueing
Promise Methods المفيدة 🎁
Promise.all()
Promise.all() دي بتخليك تشغل كذا حاجة مع بعض وتستنى لحد ما كلهم يخلصوا. زي ما تقول كده عندك ٣ طلبيات من ٣ مطاعم مختلفة، عايز تستنى لحد ما كلهم يوصلوا عشان العيلة تاكل مع بعض.
تفاصيل تنفيذ Promise.all في JavaScript 🚀
الـ Promise.all
هي واحدة من أهم الأدوات في JavaScript للتعامل مع عدة وعود (Promises) في نفس الوقت. م تيجي نفهم الأول بشرح مفصل لكيفية عملها من الداخل.
كود مشابه للـ Native Implementation
الكود اللي هكتبه يوضح الفكرة العامة عن كيفية عمل Promise.all()، لكن دي مش بالظبط هي الـ implementation الفعلية ليها في محركات JavaScript زي V8. محركات JavaScript غالبًا بتستخدم native code مكتوب بلغة مثل C++ عشان تنفذ التفاصيل، لكن الكود هيعبر بشكل دقيق عن السلوك الظاهري لـ Promise.all.
function customPromiseAll(promises) {
return new Promise((resolve, reject) => {
// التحقق من صحة المدخلات
if (!Array.isArray(promises)) {
return reject(new TypeError("لازم تدخل مصفوفة"));
}
const results = new Array(promises.length);
let completedCount = 0;
// التعامل مع المصفوفة الفارغة
if (promises.length === 0) {
resolve(results);
return;
}
promises.forEach((promise, index) => {
Promise.resolve(promise) // تحويل أي قيمة غير Promise لـ Promise
.then(result => {
results[index] = result;
completedCount++;
if (completedCount === promises.length) {
resolve(results); // لما الكل يخلص، يرجع النتائج
}
})
.catch(reject);// لو فشل واحد، كله يفشل
});
});
}
إيه اللي بيعمله الـjs Engine داخليًا؟
1. Input Validation
- أول حاجة بيتأكد إن اللي دخل هو iterable (زي array).
- لو مش iterable، بيرجع TypeError.
2. تجهيز المكان
- بيحجز مكان في الميموري للنتايج اللي هتطلع
- بيعمل promise جديد هيرجع في الآخر
3. معالجة كل عنصر
- بيلف على كل عنصر في الأراي
- بيشوف كل عنصر حالته إيه: خلص ولا لسه ولا فيه إيرور
- بيضيف handlers للي لسه مخلصش
4. تنظيم الطوابير
- بيحط الـ callbacks في الـ Microtask Queue
- بيرتب الحاجات حسب الأولوية بتاعتها
- بيتأكد إن كل حاجة هتتنفذ في وقتها
5. معالجة النتايج
- لو كل حاجة تمام، بيجمع النتايج كلها في أراي
- لو في إيرور، بيقف التنفيذ وبيرجع الإيرور على طول
- بيحرر الميموري بتاع الحاجات اللي خلصت
6. تنظيف المكان
- بيشيل أي داتا مؤقتة اتعملت
- بيتأكد إن مفيش memory leaks
- بيرجع الميموري للسيستم
مميزات Promise.all()
- Parallel Execution
- كل الـ Promises بتشتغل مع بعض بدون انتظار
- Fail Fast
- لو واحدة فشلت، كله بيترفض فورًا.
- Maintains Order
- النتايج بتفضل بنفس ترتيب المصفوفة الأصلية.
أمثلة عملية 📝
خليني أشرح لك الحالات اللي هتحتاج فيها
تحسين الأداء والسرعة
// طريقة بطيئة - كل طلب بيستنى اللي قبله
const slow = async () => {
const user = await fetchUserData(); // 3 ثواني
const posts = await fetchUserPosts(); // 3 ثواني
const comments = await fetchUserComments(); // 3 ثواني
// المجموع = 9 ثواني 😫
}
// طريقة أسرع باستخدام Promise.all
const fast = async () => {
const [user, posts, comments] = await Promise.all([
fetchUserData(), // 3 ثواني
fetchUserPosts(), // بيشتغلوا مع بعض
fetchUserComments() // مع بعض
]);
// المجموع = 3 ثواني بس! 🚀
}
الـ Promise.all بيخلي الطلبات تشتغل في نفس الوقت بدل ما كل واحد يستنى التاني، وده بيخلي الوقت الإجمالي أقل بكتير.
مثال 2: التعامل مع عمليات مرتبطة ببعض API متعددة
// نفرض إن عندنا ٣ APIs بنجيب منهم معلومات عن مستخدم
const getUserInfo = () => {
return fetch('https://api.example.com/user/123');
}
const getUserPosts = () => {
return fetch('https://api.example.com/user/123/posts');
}
const getUserFriends = () => {
return fetch('https://api.example.com/user/123/friends');
}
// هنستخدم Promise.all عشان نجيب كل البيانات دي مرة واحدة
Promise.all([
getUserInfo(),
getUserPosts(),
getUserFriends()
])
.then(results => {
const [userInfo, posts, friends] = results;
console.log('كل البيانات وصلت تمام!');
console.log('معلومات المستخدم:', userInfo);
console.log('البوستات:', posts);
console.log('الأصحاب:', friends);
})
.catch(error => {
console.log('في حاجة غلط حصلت:', error);
});
مثال 3: تحميل مجموعة ملفات أو صور:
const images = [
'صورة1.jpg',
'صورة2.jpg',
'صورة3.jpg'
];
const getImages = images.map(img => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(`تم تحميل ${img}`);
img.onerror = () => reject(`مشكلة في تحميل ${img}`);
img.src = img;
});
});
Promise.all(getImages)
.then(result => {
console.log('كل الصور اتحملت بنجاح:', result);
})
.catch(error => {
console.log('في مشكلة حصلت:', error);
});
Promise.race()
Promise.race زي ما اسمه بيقول - دي سباق! بياخد مجموعة promises وبيرجع أول واحدة تخلص، سواء نجحت أو فشلت.
// بيرجع أول promise تخلص
// مثال بسيط للفهم
const fastPromise = new Promise(resolve => setTimeout(() => resolve('أنا جيت الأول! 🥇'), 100));
const slowPromise = new Promise(resolve => setTimeout(() => resolve('أنا جيت متأخر 🥈'), 300));
Promise.race([fastPromise, slowPromise])
.then(winner => console.log(winner)) // هيطبع: "أنا جيت الأول! 🥇"
ثانياً - إزاي بيشتغل Under the Hood?
خليني أشرح الخطوات اللي بتحصل جوه:
// تقريباً كده Promise.race بيتنفذ جواه
function myPromiseRace(promises) {
return new Promise((resolve, reject) => {
// بيعمل loop على كل ال promises
for (const promise of promises) {
// بيحط .then و .catch على كل promise
Promise.resolve(promise).then(resolve, reject);
}
});
}
// شرح أعمق للي بيحصل:
const promiseRaceImplementation = (promises) => {
// ١. بيتأكد إن في array
if (!Array.isArray(promises)) {
throw new TypeError('Promises must be an array');
}
// ٢. بيعمل promise جديدة
return new Promise((resolve, reject) => {
// ٣. لو مفيش promises أصلاً
if (promises.length === 0) {
return;
}
// ٤. بيعمل loop وبيحط handlers
promises.forEach(promise => {
// بيحول أي قيمة ل promise
Promise.resolve(promise)
.then(result => {
resolve(result); // أول واحد يوصل للـ then
})
.catch(error => {
reject(error); // أو أول واحد يوصل للـ catch
});
});
});
}
ليه Promise.race موجود؟ 🤔
- تنفيذ Timeout مع API Calls أهم وأشهر استخدام - حماية تطبيقك من الـ API Calls اللي بتاخد وقت كتير
- مثال: لو كنت بتعمل request لـ API ومش عايز تنتظر أكتر من ٥ ثواني، تقدر تستخدم Promise.race بين الـ fetch و timeout.
// مثال عملي: عايز تجيب داتا بس مش هتستنى أكتر من ٥ ثواني
const safeFetchData = async () => {
try {
const response = await Promise.race([
fetch('https://api.example.com/data'),
// We will wait for 5 seconds only
new Promise((_, reject) =>
setTimeout(() => reject('الخدمة بطيئة جداً 😴'), 5000)
)
]);
return await response.json(); // لو خلصت في الوقت المحدد، هنرجع الداتا
} catch (error) {
if (error === 'الخدمة بطيئة جداً 😴') {
// ممكن نجرب سيرفر تاني
// أو نعرض رسالة للمستخدم
// أو نجيب الداتا من الكاش
}
throw error; // لو في خطأ تاني، هنرفعه
}
}
- تحسين تجربة المستخدم (UX) لما يكون عندك كذا مصدر للداتا وعايز تجيب أسرعهم:
const getUserAvatar = async (userId) => {
const imageSources = [
`https://mainserver.com/users/${userId}/avatar`,
`https://backupserver1.com/users/${userId}/avatar`,
`https://backupserver2.com/users/${userId}/avatar`,
];
try {
// هنجيب أول صورة تتحمل
const fastestImage = await Promise.race(
imageSources.map(async url => {
const response = await fetch(url);
if (!response.ok) throw new Error(`فشل تحميل ${url}`); // لو فيه مشكلة في التحميل
return response.blob();
})
);
return URL.createObjectURL(fastestImage); // هنرجع رابط الصورة اللي اتحملت
} catch (error) {
// لو كل المصادر فشلت، نرجع صورة افتراضية
return '/default-avatar.png';
}
}
- تحسين الأداء في الشات والألعاب مثال على شات في لعبة
const sendMessageToPlayer = async (messageData) => {
const channels = [
sendViaWebSocket(messageData),
sendViaWebRTC(messageData),
sendViaHTTP(messageData)
];
try {
// نستخدم أسرع قناة اتصال متاحة
await Promise.race(channels);
console.log('تم إرسال الرسالة بنجاح! ✅');
} catch {
// لو فشلت كل القنوات
console.error('فشل إرسال الرسالة ❌');
}
}
- التعامل مع حالات عدم الاستقرار في الشبكة
const safeLoadContent = async () => {
// قائمة CDNs المتاحة
const cdns = [
'https://cdn1.example.com',
'https://cdn2.example.com',
'https://cdn3.example.com'
];
const loadAttempts = cdns.map(async cdn => {
const response = await fetch(`${cdn}/content.json`);
if (!response.ok) throw new Error(`${cdn} مش شغال`); // لو الـ CDN مش شغال
return response.json();
});
try {
// نجيب من أسرع CDN متاح
const content = await Promise.race(loadAttempts);
localStorage.setItem('content', JSON.stringify(content)); // حفظ المحتوى في الكاش
return content;
} catch {
// نرجع نسخة قديمة من الكاش
return JSON.parse(localStorage.getItem('content'));
}
}
Promise.race فعلاً مفيد في السيناريوهات اللي فيها توقيت حساس أو لما تحتاج تتعامل مع أسرع حل، بدل ما تضيع وقتك في الانتظار.
promises.allSettled
لو عندك مجموعة promises، هتستنى لحد ما كلهم يخلصوا - سواء نجحوا أو فشلوا - وترجع لك نتيجة كل واحد فيهم.
const نتائج = await Promise.allSettled([
// الوعد ده هينجح
Promise.resolve("تمام 👍"),
// الوعد ده هيفشل
Promise.reject("في مشكلة 😢"),
// الوعد ده هينجح
Promise.resolve("تمام برضو 👍")
]);
console.log(نتائج);
/* هيطبع:
[
{ status: "fulfilled", value: "تمام 👍" },
{ status: "rejected", reason: "في مشكلة 😢" },
{ status: "fulfilled", value: "تمام برضو 👍" }
]
*/
- تحميل بيانات مستخدمين كتير مرة واحدة
async function getUsersData(usersList) {
const attempts = usersList.map(async user => {
try {
const response = await fetch(`/api/users/${user.id}`);
return await response.json();
} catch (error) {
return { error: `مشكلة في تحميل بيانات ${user.name}` }; // لو حصل مشكلة في تحميل بيانات المستخدم
}
});
const results = await Promise.allSettled(attempts); // ننتظر كل الـ Promises حتى لو فشلوا أو نجحوا
// نفرز النتائج
const success = [];
const errors = [];
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
success.push(result.value); // لو النجاح
} else {
errors.push({
user: usersList[index], // المستخدم اللي فشل في تحميل بياناته
errorReason: result.reason // سبب المشكلة
});
}
});
return { success, errors };
}
// Usage
const results = await getUsersData([
{ id: 1, name: 'أحمد' },
{ id: 2, name: 'محمد' },
{ id: 3, name: 'علي' }
]);
- رفع صور كتير مرة واحدة
async function uploadImages(imagesList) {
const uploadAttempts = imagesList.map(async image => {
const formData = new FormData();
formData.append('file', image);
try {
const response = await fetch('/api/upload', {
method: 'POST',
body: formData
});
return await response.json();
} catch (error) {
return { error: `مشكلة في رفع ${image.name}` }; // لو حصل مشكلة في رفع الصورة
}
});
const results = await Promise.allSettled(uploadAttempts); // ننتظر كل محاولات الرفع حتى لو فشلوا أو نجحوا
// Detailed report about each image's result
return results.map((result, index) => ({
imageName: imagesList[index].name, // اسم الصورة
uploaded: result.status === 'fulfilled', // لو الرفع تم بنجاح
result: result.status === 'fulfilled' ? result.value : result.reason // النتيجة أو السبب لو فشل
}));
}
ليه نستخدم Promise.allSettled?
مش بتوقف عند أول error
- عكس Promise.all اللي بتوقف أول ما تلاقي مشكلة
- بتكمل لآخر promise وتجيب كل النتائج
بتديك تقرير كامل
- بتعرف مين نجح ومين فشل
- بتقدر تتعامل مع كل حالة على حدة
مثالية للـ batch operations
- رفع ملفات كتير
- تحديث بيانات كتير
- فحص خدمات متعددة
أحسن للـ error handling
- مش محتاج try/catch كتير
- بتقدر تتعامل مع الأخطاء بشكل منظم
مرونة في التعامل مع النتائج
- تقدر تفرز النتائج بسهولة
- تقدر تعمل تقارير مفصلة
- تقدر تتعامل مع النجاح والفشل بشكل منفصل
الفرق بينهم
Promise.all:
- بياخد array من promises وبيستنى كلهم يخلصوا.
- لو أي promise فيهم rejected هيرجع reject على طول.
- بيرجع array فيه نتايج كل الـpromises بنفس الترتيب.
- مفيد لما تحتاج تنفذ حاجات موازية وتستنى كلهم يخلصوا.
Promise.race:
- بيرجع أول promise يخلص سواء resolve أو reject.
- بمجرد ما واحد يخلص بيرجع نتيجته على طول.
- الباقي هيكمل في الباكجراوند بس مش هناخد نتيجتهم.
- مفيد في حالات الـtimeout أو لما عايز أسرع response.
Promise.allSettled:
- بيستنى كل الـpromises يخلصوا زي الـall.
- بيرجع array فيه status و value/reason لكل promise.
- مش بيهمه لو في rejected promises.
- كل promise هيرجع object فيه status (fulfilled/rejected) والـvalue بتاعته.
- مفيد لما عايز تعرف نتيجة كل الـpromises مهما كانت.
الاختلافات الرئيسية:
- Promise.all: بيفشل لو في rejected promise.
- Promise.race: بياخد أول واحد يخلص.
- Promise.allSettled: بيديك نتيجة كل واحد مهما حصل.
AbortController
لسه هكتبلك عنها مقالة بالتفصيل بس دا مجرد مثال لفتح أفاق تفكير مش أكتر
الـabort controller ببساطة هو مفتاح stop للـ requests والعمليات اللي بتاخد وقت. تعالى نشوف مثال متقدم:
class DataService {
constructor() {
// كل request هيبقى ليه controller خاص بيه
this.controllers = new Map();
}
async fetchData(endpoint, timeout = 5000) {
// بنعمل controller جديد للrequest ده
const controller = new AbortController();
const id = Date.now();
this.controllers.set(id, controller);
try {
// بنعمل race بين الrequest والtimeout
const response = await Promise.race([
fetch(endpoint, {
signal: controller.signal,
// options تانية...
}),
new Promise((_, reject) => {
setTimeout(() => {
controller.abort();
reject(new Error('Request timeout'));
}, timeout);
})
]);
// لو نجح، بنمسح الcontroller
this.controllers.delete(id);
return await response.json();
} catch (error) {
// لو حصل error بنشوف نوعه
if (error.name === 'AbortError') {
throw new Error('Request was cancelled');
}
throw error;
} finally {
// تنظيف
this.controllers.delete(id);
}
}
// ميثود عشان نcancel كل الrequests الشغالة
cancelAllRequests() {
this.controllers.forEach(controller => {
controller.abort();
});
this.controllers.clear();
}
// نcancel request معين
cancelRequest(id) {
const controller = this.controllers.get(id);
if (controller) {
controller.abort();
this.controllers.delete(id);
}
}
}
المميزات المتقدمة في المثال ده:
- بنستخدم Map عشان نtrack كل الـrequests الشغالة.
- كل request ليه timeout.
- بنستخدم Promise.race بين الـrequest والـtimeout.
- فيه handling للـerrors المختلفة.
- فيه cleanup منظم.
- القدرة على إلغاء request واحد أو كل الـrequests.
ده الـuse case الشائع للـAbort Controller بس ممكن نستخدمه مع أي حاجة:
- Web Sockets
- Event Listeners
- Animations
- Intensive Computations
الفكرة الرئيسية:
إنه بيدينا القدرة نcontrol العمليات اللي بتاخد وقت وده مهم في:
- تحسين الـUX.
- منع الـmemory leaks.
- إدارة الـresources.
- التحكم في الـnetwork requests.