مرحباً بكم في درس جديد! اليوم سنتعمق في فهم وعود الجافا سكريبت (Promises) وسنتعلم كيفية استخدامها بفعالية لتنظيم الكود غير المتزامن وجعله أكثر قابلية للقراءة والصيانة.
الخطوة 1: فهم أساسيات الـ Promise وإنشائها
الـ Promise في جافا سكريبت هو كائن يمثل إكمالاً أو فشلاً مستقبلياً لعملية غير متزامنة. لنقم بإنشاء وعد بسيط يحاكي عملية تستغرق وقتاً.
الـ Promise يمتلك ثلاث حالات:pending(قيد الانتظار)،fulfilled(تم الإنجاز بنجاح)،rejected(تم الرفض/الفشل).
// إنشاء Promise جديد
const myFirstPromise = new Promise((resolve, reject) => {
// محاكاة عملية غير متزامنة تستغرق 2 ثانية
setTimeout(() => {
const success = true; // يمكن تغيير هذا لاختبار حالة الفشل
if (success) {
// إذا نجحت العملية، نستدعي resolve مع القيمة الناتجة
resolve("البيانات تم جلبها بنجاح!");
} else {
// إذا فشلت العملية، نستدعي reject مع رسالة الخطأ
reject("فشل في جلب البيانات.");
}
}, 2000); // تأخير لمدة 2 ثانية
});
console.log("الـ Promise تم إنشاؤه وبدأ العمل...");
في هذا الجزء، قمنا بإنشاء Promise جديد يأخذ دالة تنفيذ (executor function) كوسيط. هذه الدالة بدورها تستقبل دالتين: resolve و reject. يتم استدعاء resolve عند نجاح العملية، و reject عند فشلها. استخدمنا setTimeout لمحاكاة عملية غير متزامنة.
الخطوة 2: التعامل مع نتائج الـ Promise باستخدام .then() و .catch()
بعد إنشاء الـ Promise، نحتاج إلى طريقة للتعامل مع نتيجته (نجاح أو فشل). هنا يأتي دور الدالتين .then() و .catch().
// استخدام .then() للتعامل مع النجاح
myFirstPromise.then((message) => {
console.log("نجاح:", message); // تطبع الرسالة عند نجاح الـ Promise
}).catch((error) => {
// استخدام .catch() للتعامل مع الفشل
console.error("خطأ:", error); // تطبع رسالة الخطأ عند فشل الـ Promise
}).finally(() => {
// استخدام .finally() لتنفيذ كود بغض النظر عن النتيجة (نجاح أو فشل)
console.log("العملية اكتملت، سواء بنجاح أو فشل.");
});
console.log("تم إعداد معالجات الـ Promise.");
الدالة .then() تُستدعى عند استدعاء resolve داخل الـ Promise، وتستقبل القيمة التي تم تمريرها إلى resolve. أما .catch() فتُستدعى عند استدعاء reject وتستقبل رسالة الخطأ. .finally() تُنفذ دائماً بغض النظر عن نتيجة الـ Promise.
الخطوة 3: ربط الوعود (Chaining Promises) لتسلسل العمليات
إحدى أقوى ميزات الـ Promises هي القدرة على ربطها معاً لتنفيذ سلسلة من العمليات غير المتزامنة بشكل تسلسلي ومنظم. كل .then() يمكن أن يعيد Promise جديداً.
ربط الوعود (Promise Chaining) يحل مشكلة "Callback Hell" ويجعل الكود أكثر قابلية للقراءة.
function fetchData(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">جلب بيانات للمستخدم ID: ${id}</code>);
resolve({ id: id, name: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">User ${id}</code>, data: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Data for user ${id}</code> });
}, 1000);
});
}
function processData(userData) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">معالجة بيانات المستخدم: ${userData.name}</code>);
userData.processed = true;
resolve(userData);
}, 800);
});
}
function displayData(processedUserData) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">عرض بيانات المستخدم: ${processedUserData.name}</code>);
console.log("البيانات النهائية:", processedUserData);
resolve("تم عرض جميع البيانات بنجاح.");
}, 500);
});
}
// ربط الوعود
fetchData(123) // البدء بجلب البيانات للمستخدم 123
.then(userData => processData(userData)) // بعد الجلب، معالجة البيانات
.then(processedUserData => displayData(processedUserData)) // بعد المعالجة، عرض البيانات
.then(finalMessage => {
console.log("العملية التسلسلية اكتملت:", finalMessage);
})
.catch(error => {
console.error("حدث خطأ في سلسلة الوعود:", error);
});
هنا، كل دالة (fetchData، processData، displayData) تعيد Promise. وهذا يسمح لنا بربطها باستخدام .then()، حيث يتم تمرير نتيجة الـ Promise السابق كوسيط لـ .then() التالي. هذا يخلق تدفقاً واضحاً ومنطقياً للعمليات.
الكود النهائي الكامل
هذا هو السكربت كاملاً، جاهز للتشغيل في بيئة Node.js.
// الخطوة 1: فهم أساسيات الـ Promise وإنشائها
const myFirstPromise = new Promise((resolve, reject) => {
// محاكاة عملية غير متزامنة تستغرق 2 ثانية
setTimeout(() => {
const success = true; // يمكن تغيير هذا لاختبار حالة الفشل
if (success) {
// إذا نجحت العملية، نستدعي resolve مع القيمة الناتجة
resolve("البيانات تم جلبها بنجاح!");
} else {
// إذا فشلت العملية، نستدعي reject مع رسالة الخطأ
reject("فشل في جلب البيانات.");
}
}, 2000); // تأخير لمدة 2 ثانية
});
console.log("الـ Promise تم إنشاؤه وبدأ العمل...");
// الخطوة 2: التعامل مع نتائج الـ Promise باستخدام .then() و .catch()
myFirstPromise.then((message) => {
console.log("نجاح:", message); // تطبع الرسالة عند نجاح الـ Promise
}).catch((error) => {
// استخدام .catch() للتعامل مع الفشل
console.error("خطأ:", error); // تطبع رسالة الخطأ عند فشل الـ Promise
}).finally(() => {
// استخدام .finally() لتنفيذ كود بغض النظر عن النتيجة (نجاح أو فشل)
console.log("العملية اكتملت، سواء بنجاح أو فشل.");
});
console.log("تم إعداد معالجات الـ Promise.");
// الخطوة 3: ربط الوعود (Chaining Promises) لتسلسل العمليات
function fetchData(id) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">جلب بيانات للمستخدم ID: ${id}</code>);
resolve({ id: id, name: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">User ${id}</code>, data: <code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">Data for user ${id}</code> });
}, 1000);
});
}
function processData(userData) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">معالجة بيانات المستخدم: ${userData.name}</code>);
userData.processed = true;
resolve(userData);
}, 800);
});
}
function displayData(processedUserData) {
return new Promise((resolve) => {
setTimeout(() => {
console.log(<code dir="ltr" style="background:#f3f4f6; color:#0056b3; padding:2px 6px; border-radius:4px; font-family:monospace; direction:ltr !important; display:inline-block;">عرض بيانات المستخدم: ${processedUserData.name}</code>);
console.log("البيانات النهائية:", processedUserData);
resolve("تم عرض جميع البيانات بنجاح.");
}, 500);
});
}
console.log("\nبدء سلسلة الوعود:");
// ربط الوعود
fetchData(456) // البدء بجلب البيانات للمستخدم 456
.then(userData => processData(userData)) // بعد الجلب، معالجة البيانات
.then(processedUserData => displayData(processedUserData)) // بعد المعالجة، عرض البيانات
.then(finalMessage => {
console.log("العملية التسلسلية اكتملت:", finalMessage);
})
.catch(error => {
console.error("حدث خطأ في سلسلة الوعود:", error);
});
النتيجة المتوقعة
عند تشغيل السكربت، سترى تسلسلاً للأحداث في الكونسول يوضح كيفية عمل الوعود. أولاً، سيتم طباعة رسائل فورية عن بدء إنشاء الـ Promise ومعالجاته. بعد ثانيتين، سترى رسالة "نجاح" من الـ Promise الأول ورسالة "العملية اكتملت". بعد ذلك، ستبدأ سلسلة الوعود الثانية، حيث ستظهر رسائل "جلب بيانات"، ثم "معالجة بيانات"، ثم "عرض بيانات"، وأخيراً رسالة اكتمال السلسلة، كل منها بتأخير زمني يعكس العمليات غير المتزامنة.
الـ Promise تم إنشاؤه وبدأ العمل...
تم إعداد معالجات الـ Promise.
بدء سلسلة الوعود:
جلب بيانات للمستخدم ID: 456
معالجة بيانات المستخدم: User 456
عرض بيانات المستخدم: User 456
البيانات النهائية: { id: 456, name: 'User 456', data: 'Data for user 456', processed: true }
العملية التسلسلية اكتملت: تم عرض جميع البيانات بنجاح.
نجاح: البيانات تم جلبها بنجاح!
العملية اكتملت، سواء بنجاح أو فشل.
ملاحظة: ترتيب ظهور رسائل النجاح/الفشل للـ Promise الأول وسلسلة الوعود قد يختلف قليلاً بسبب طبيعة العمليات غير المتزامنة، ولكن النتائج النهائية ستكون كما هو موضح.