JavaScript: البرمجة غير المتزامنة في JavaScript
آخر تحديث: 2026-10-13
JavaScript أحادي الخيط — يمكنه فعل شيء واحد فقط في كل مرة. لكن إذا استغرق طلب الشبكة 3 ثواني، هل يجب أن تتجمد الصفحة لـ 3 ثواني؟ بالطبع لا. البرمجة غير المتزامنة تحل هذا: بدلاً من الانتظار، تأخذ رقماً وتعود عندما يكون جاهزاً. مثل نظام طابور المطعم — لا تقف عند باب المطبخ، بل تجلس وتنتظر استدعاء رقمك.
1. متزامن مقابل غير متزامن
متزامن: الكود ينفذ سطر بسطر — السطر التالي ينتظر انتهاء الحالي. مثل الوقوف في الصف — تنتظر حتى ينتهي الشخص أمامك.
غير متزامن: عمليات معينة (طلبات الشبكة، المؤقتات، إدخال/إخراج الملفات) تبدأ دون عرض الكود اللاحق. عندما تنتهي، يتم إخطارك عبر استدعاء أو Promise.
<script>
console.log('1');
setTimeout(function() {
console.log('2');
}, 1000);
console.log('3');
</script>
ترتيب الإخراج هو 1 → 3 → 2. setTimeout غير متزامن — الاستدعاء ينفذ بعد ثانية واحدة دون عرض console.log('3').
الاستدعاءات هي النمط الأساسي للبرمجة غير المتزامنة — تمرير دالة كمعامل، وتُستدعى عندما تكتمل العملية غير المتزامنة.
▶ مثال: استخدام أساسي للاستدعاء
▶ جرّب الكود(1) جحيم الاستدعاءات (Callback Hell)
عندما تعتمد العمليات غير المتزامنة على بعضها تسلسلياً، تتداخل الاستدعاءات أعمق وأعمق — هذا هو "جحيم الاستدعاءات" سيئ السمعة.
<script>
getUser(function(user) {
getOrders(user.id, function(orders) {
getOrderDetail(orders[0].id, function(detail) {
getItems(detail.itemId, function(items) {
console.log('4 مستويات عميقة — أضف تعقيداً أكثر ويصبح غير مقروء');
});
});
});
});
</script>
2. Promise
Promise هو كائن يمثل النتيجة النهائية لعملية غير متزامنة. لديه ثلاث حالات:
| الحالة | الوصف |
|---|---|
pending |
قيد التنفيذ، لا نتيجة بعد |
fulfilled |
اكتملت بنجاح، النتيجة متاحة |
rejected |
فشلت، الخطأ متاح |
بمجرد انتقال Promise من pending إلى fulfilled أو rejected، لا تتغير مرة أخرى — مثل سهم أُطلق، لا يمكن استدعاؤه.
(1) إنشاء Promise
<script>
const promise = new Promise(function(resolve, reject) {
// عملية غير متزامنة
// استدعاء resolve(result) عند النجاح
// استدعاء reject(error) عند الفشل
});
</script>
(2) then / catch / finally
<script>
promise
.then(function(result) { console.log(result); }) // ينفذ عند النجاح
.catch(function(error) { console.error(error); }) // ينفذ عند الفشل
.finally(function() { console.log('تم'); }); // ينفذ بغض النظر
</script>
▶ مثال: محاكاة طلب بـ Promise
▶ جرّب الكود(3) التسلسل
then نفسها تُرجع Promise، لذا يمكنك تسلسل الاستدعاءات — جحيم الاستدعاءات يصبح خطاً مباشراً.
<script>
fetchUser()
.then(function(user) { return fetchOrders(user.id); })
.then(function(orders) { return fetchDetail(orders[0].id); })
.then(function(detail) { console.log(detail); })
.catch(function(err) { console.error(err); });
</script>
3. async / await
async/await هو سكر صناعي فوق Promises — يجعل الكود غير المتزامن يبدو متزامناً، مما يحسن القراءة بشكل كبير.
async: يعلن دالة غير متزامنة تُرجع Promise تلقائياًawait: يوقف التنفيذ حتى تحل Promise، ثم يُرجع النتيجة
▶ مثال: إعادة كتابة الطلب أعلاه بـ async/await
▶ جرّب الكودasync/await يحول "خذ رقماً وانتظر" إلى "ابقَ في الصف دون مغادرة" — تدفق الكود مباشقر والقراءة ترتفع بشكل كبير.
(1) معالجة الأخطاء
async/await يستخدم try/catch لمعالجة الأخطاء — نفس النمط المستخدم مع الكود المتزامن.
<script>
async function loadAll() {
try {
const user = await fetchUser();
const orders = await fetchOrders(user.id);
console.log(orders);
} catch (err) {
console.error('حدث خطأ ما:', err);
}
}
</script>
4. Promise.all و Promise.race
| الطريقة | الوصف |
|---|---|
Promise.all([p1, p2, p3]) |
ينجح فقط إذا نجح الجميع؛ يفشل بمجرد فشل واحد |
Promise.race([p1, p2, p3]) |
يستخدم نتيجة أيهما يحل أولاً (نجاح أو فشل) |
▶ مثال: طلبات متزامنة مع Promise.all
▶ جرّب الكودPromise.all مثل عشاء جماعي — يجب أن يصل الجميع قبل أن تأكل. Promise.race مثل سباق — من ينتهي أولاً يفوز. استخدم all للطلبات المستقلة لتسريع الأشياء، و race عندما يكون لديك عدة مصادر مرآة وتريد الأسرع.
❓ أسئلة شائعة
await في دالة عادية؟await يمكن استخدامه فقط داخل دالة async. استخدام await في دالة عادية يسبب خطأ صيغة. await المسطح مدعوم في الوحدات، لكن الدعم في المتصفحات يختلف.Promise.all؟Promise.all يرفض فوراً بالخطأ الأول. Promises الأخرى لا تزال تُنفذ، لكن نتائجها تُتجاهل. إذا كنت بحاجة إلى "الجميع يحل بغض النظر عن النتيجة"، استخدم Promise.allSettled.async؟async تُرجع دائماً Promise. حتى لو return 42، في الواقع تُرجع Promise.resolve(42). لذا يمكنك استخدام .then() لاستهلاك النتيجة.📖 ملخص
- الكود المتزامن يعرض التنفيذ اللاحق؛ غير المتزامن لا يعرض — أحادي الخيط لا يزال يمكنه "فعل أشياء متعددة"
- الاستدعاءات هي النمط الأساسي؛ التداخل العميق يخلق جحيم الاستدعاءات
- Promise لديه ثلاث حالات:
pending → fulfilledأوpending → rejected— انتقالات الحالة لا رجعة فيها then/catch/finallyتستهلك Promises؛ التسلسل يحل جحيم الاستدعاءاتasync/awaitهو سكر صناعي فوق Promises؛awaitيمكن استخدامه فقط داخل دوالasyncPromise.allينتظر نجاح الجميع؛Promise.raceيأخذ أول من يحل
📝 تمارين
- أساسي: استخدم
Promise+setTimeoutلمحاكاة تأخير ثانيتين، ثم اطبع "تم" عند النجاح. - متوسط: أعد كتابة أعلاه بـ
async/awaitوأضف معالجة أخطاءtry/catch. - تحدي: استخدم
Promise.allلإطلاق 3 طلبات محاكاة بتأخيرات مختلفة (1 ثانية/2 ثانية/3 ثانية) متزامنة، ثم احسب واعرض الوقت الكلي المنقضي عندما تكتمل جميعها.