Kodokon kodokon.com

الشيفرة غير المتزامنة بعمق: الوعود، async/await، معالجة الأخطاء

استثمر حلقة الأحداث، ووازِ وعودك بشكل صحيح، واجعل معالجة الأخطاء غير المتزامنة موثوقة.

11 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

يعتمد نموذج التنفيذ على طابور مهام كبرى (setTimeout والدخل/الخرج) وطابور مهام صغرى (.then وawait وqueueMicrotask). بعد كل مهمة، يفرّغ المحرك طابور المهام الصغرى بالكامل قبل الانتقال. والنتيجة المباشرة: يعمل وعد محلول دائما قبل setTimeout(fn, 0)، وقد تجمّد سلسلة مهام صغرى مفرطة الطول عرض الصفحة تماما كحلقة متزامنة.

JAVASCRIPT
console.log("start");
setTimeout(() => console.log("macrotask"), 0);
Promise.resolve().then(() => console.log("microtask"));
console.log("end");
// start, end, microtask, macrotask
تعمل المهام الصغرى قبل أي مهمة كبرى معلّقة.

async/await سكّر لغوي فوق الوعود، لكنه يخفي مزلق أداء: كل await داخل حلقة يسلسل العمليات. فإن كانت الطلبات مستقلة، فأطلقها جميعا أولا ثم انتظر الدفعة بـ Promise.all. فلثلاثة طلبات بزمن 100 مِلّي ثانية، تكلّف النسخة التسلسلية 300 مِلّي ثانية، والنسخة المتوازية 100 مِلّي ثانية.

JAVASCRIPT
const fetchUser = (id) => new Promise((res) => {
  setTimeout(() => res({ id }), 100);
});
async function loadSequential(ids) {
  const users = [];
  for (const id of ids) {
    users.push(await fetchUser(id)); // ~100 ms each
  }
  return users;
}
async function loadParallel(ids) {
  return Promise.all(ids.map(fetchUser)); // ~100 ms total
}
نفس النتيجة، لكن الكمون مقسوم على عدد الاستدعاءات.

اختر المُركِّب الصحيح وفق عقد الفشل الذي تحتاجه: يرفض Promise.all عند أول فشل (الكل أو لا شيء)، وينتظر allSettled الجميع ويصف كل نتيجة، ويأخذ race أول تسوية (نجاحا كانت أم فشلا، مفيدة لمهلة زمنية)، ويأخذ any أول نجاح ولا يرفض إلا إذا فشل كل شيء. وللوحة قيادة يجب أن تعرض نتائج جزئية، يكون allSettled هو الخيار الصحيح دائما تقريبا.

JAVASCRIPT
const tasks = [
  Promise.resolve("profile loaded"),
  Promise.reject(new Error("network unavailable")),
];
const results = await Promise.allSettled(tasks);
for (const r of results) {
  if (r.status === "fulfilled") {
    console.log("success:", r.value);
  } else {
    console.error("failure:", r.reason.message);
  }
}
تُوصف كل نتيجة؛ ولا يخفي أي فشل الآخرين.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. بأي ترتيب يعمل console.log متزامن، واستدعاء Promise.resolve().then، واستدعاء setTimeout(fn, 0)؟
    • المتزامن، setTimeout، ثم then
    • المتزامن، then، ثم setTimeout
    • then، ثم المتزامن، ثم setTimeout
  2. تطلق عشرة طلبات مستقلة. أي مقاربة تقلّل الكمون الإجمالي؟
    • await واحد لكل طلب داخل حلقة for...of
    • إنشاء كل الوعود، ثم انتظار Promise.all
    • تسلسل استدعاءات .then واحدا إثر آخر
    • استعمال setTimeout للمباعدة بين الاستدعاءات
  3. لماذا يفشل try { doAsync(); } catch (e) {} بلا await في التقاط الرفض من doAsync؟
    • لأن catch لا يعمل داخل الدوال غير المتزامنة
    • لأن الرفض يحدث بعد أن تكون كتلة try قد خرجت، بما أن الوعد لا يُنتظَر أبدا
    • لأن الوعود لا يمكن التقاطها إلا بـ .catch