1.2 · غير المتزامن: رد الاتصال (Callbacks) والوعود (Promises) وasync/await

المستوى 1 · جافاسكريبت وأساسيات Node.js

1.2غير المتزامن: رد الاتصال (Callbacks) والوعود (Promises) وasync/await

الهدف: فهم غير المتزامن والتعامل معه بأشكاله الثلاثة (رد الاتصال، الوعود، async/await)، وتفضيل async/await — المنعطف المركزي في Node.
الوقت المقدّر: 13 دقيقة · قراءة ذاتية · إذا كان لديك 5 دقائق فقط: اقرأ «الأساسي في 30 ثانية» + المخطط.

الأساسي في 30 ثانية

  • غير المتزامن = «عندما يجهز»: العمليات البطيئة تنطلق ونتيجتها تصل لاحقًا — ولا يمكن قراءتها في السطر التالي.
  • رد الاتصال ← جحيم رد الاتصال: اتفاقية error-first (الخطأ في الوسيط الأول) تعمل، لكن دوال رد الاتصال التابعة تتداخل في هرم غير مقروء.
  • يُفضَّل async/await: await تنتظر دون حظر، والكود يُقرأ كالمتزامن، والأخطاء عبر try/catch — مبنية على الوعود (Promises).

حالة مستمرة — ساليم ينتظر ملفاته في الجزائر

يجب على ساليم قراءة ملف إعدادات، ثم استجواب قاعدة بيانات بمحتواه، ثم استدعاء API: كتب ثلاث دوال رد اتصال متداخلة وتاه في المسافات البادئة — والأسوأ أن console.log لديه يعرض undefined لأنه يقرأ النتيجة قبل وصولها. أعاد كتابة الكل بـ async/await مع try/catch: ثلاثة أسطر await مقروءة بالترتيب، والخطأ مُدار في مكان واحد. اختفى خطأ الـ undefined مع النموذج الذهني القديم.

تكملة الحالة 1.1 — ينتقل ساليم من اللغة إلى قلب Node: غير المتزامن.

1. من رد الاتصال إلى جحيم رد الاتصال

غير المتزامن هو المفهوم المركزي في Node: العمليات البطيئة (قراءة ملف، استجواب قاعدة، استدعاء API) تنطلق ونتيجتها تصل لاحقًا. الآلية التاريخية هي دالة رد الاتصال (Callback): دالة تُمرَّر كوسيط، تُستدعى عند نهاية العملية — مع اتفاقية error-first (الخطأ في الوسيط الأول، null إذا سار كل شيء بخير). تسلسل عمليات تابعة يقود إلى جحيم رد الاتصال (Callback Hell): هرم غير مقروء، صعب الإدارة خاصة للأخطاء.

يكتب ساليم fs.readFile('config.txt', (err, data) => ...) ويلاحظ اتفاقية error-first: اختبار err أولًا، ثم استغلال data — وليس العكس أبدًا.

2. الوعود (Promises): القيمة المستقبلية المسلسلة

الوعد (Promise) يمثل قيمة مستقبلية — نتيجة عملية لم تنته بعد. ثلاث حالات: pending (انتظار)، fulfilled (نجاح، قيمة)، rejected (خطأ). نتفاعل مع .then() (نجاح) و.catch() (خطأ)، والوعود تتسلسل بدل التداخل — أكثر مقروئية بكثير من جحيم رد الاتصال.

يعيد ساليم كتابة قراءة الملف بـ fs.promises.readFile(...).then().catch(): السلسلة تحل محل الهرم والخطأ يستقر في .catch() واحد.

3. async/await: المقروئية (يُفضَّل)

الثورة الحقيقية في المقروئية هي async/await: داخل دالة async، await تنتظر النتيجة دون حظر (سكر نحوي فوق الوعود) والكود غير المتزامن يُقرأ كالمتزامن، سطرًا بعد سطر، مع try/catch كلاسيكية للأخطاء. هذا هو الأسلوب السائد والموصى به: ستصادفون الأشكال الثلاثة في الكود الموجود، لكنكم ستكتبون أساسًا بـ async/await.

ينهي ساليم مع const data = await fs.promises.readFile(...) داخل try/catch: ثلاث عمليات في ثلاثة أسطر مرتبة — صار كوده غير المتزامن يُقرأ أخيرًا كوصفة.

خطأ شائع في الفهم: «await تحظر البرنامج كـ sleep» أو «يمكن قراءة النتيجة في السطر التالي للاستدعاء». خطأ. await تعلّق فقط الدالة async بانتظار الوعد، دون حظر حلقة الأحداث — وتستمر الطلبات الأخرى في المعالجة. ونتيجة عملية غير متزامنة غير موجودة بعد في السطر التالي: يجب استهلاكها في دالة رد الاتصال أو .then() أو بعد await.
تطور غير المتزامن نحو المقروئية دوال رد الاتصال error-first،خطر جحيم رد الاتصالالوعود قيمة مستقبلية،تسلسل .then/.catchasync/await يُقرأكالمتزامن، try/catch
نحو المقروئية
نص المخطط (SEO / قارئات الشاشة / الترجمة): 1) أطلِق، لا تنتظر: يُطلق ساليم قراءة الملف ويعطي دالة رد اتصال «عندما يجهز» (error-first) بدل قراءة النتيجة في السطر التالي. 2) سلسِل: يستبدل دوال رد الاتصال المتداخلة بوعود بـ .then().catch() — انتهى زمن الهرم. 3) سطِّح: ينتقل إلى async/await داخل try/catch، ثلاث عمليات في ثلاثة أسطر مقروءة. الدرس: التمييز بين «الإطلاق» و«الحصول على النتيجة» هو النموذج الذهني الذي يفتح كل Node.

الهرم الجهنمي (لا تفعلوا هذا أبدًا)

«أسلسل ست دوال رد اتصال متداخلة، وما زلت أتتبعها... عند المستوى الثالث.»
الحكم: عند المستوى الرابع، حتى الكاتب نفسه لا يتتبعها — وإدارة الأخطاء مبعثرة في كل مكان.
«يعرض console.log لدي undefined، إذن الملف فارغ.»
الحكم: الملف ممتلئ، وقراءتكم هي التي تصل مبكرًا — النتيجة غير موجودة بعد في السطر التالي.

العبرة: التداخل دفن للنفس — والتسطيح مع await تنفُّس.

🔎 مثال ملموس — 🔎 مثال عملي — الكود نفسه بـ async/await

مقروء، غير حاظر، أخطاء مُدارة — الأسلوب الذي يجب كتابته.

const fs = require("fs");
async function lire() {
  try {
    const data = await fs.promises.readFile("f.txt", "utf8");
    console.log(data);
  } catch (err) {
    console.error("Echec :", err.message);
  }
}
lire();

القراءة: await تنتظر المحتوى دون حظر؛ وعند الفشل يعرض catch الرسالة.

الدرس: Promise.all لإطلاق عدة عمليات مستقلة بالتوازي.

مفردات القسم

دالة رد الاتصال (Callback)
دالة تُمرَّر كوسيط، تُستدعى عندما تنتهي عملية غير متزامنة. اتفاقية Node ‏error-first: الوسيط الأول = الخطأ (null إذا سار كل شيء بخير)، ثم النتيجة.

مثال بسيط: الـ (err, data) => ... الخاصة بقراءة ملف ساليم.

ما ليس كذلك: دالة تُستدعى فورًا بنتيجة جاهزة مسبقًا.

رد فعل: اختبر err أولًا، ثم استغل النتيجة.

جحيم رد الاتصال (Callback Hell)
تداخل غير مقروء لدوال رد الاتصال عند تسلسل عمليات غير متزامنة تابعة؛ مشكلة حلّتها الوعود وasync/await.

مثال بسيط: الهرم ذو المستويات الثلاثة في سكربت ساليم الأول.

ما ليس كذلك: ثلاث await مصطفّة بمقروئية داخل دالة async.

رد فعل: متى تداخل، انتقل إلى الوعود.

الوعد (Promise)
كائن يمثل قيمة مستقبلية. ثلاث حالات: pending وfulfilled (نجاح) وrejected (خطأ). التفاعل عبر .then() و.catch().

مثال بسيط: الوعد المُرجَع من fs.promises.readFile لدى ساليم.

ما ليس كذلك: محتوى الملف متاح في السطر التالي.

رد فعل: .‏then للنجاح، و.catch للخطأ.

async/await
صياغة حديثة مبنية على الوعود: await تنتظر النتيجة دون حظر؛ والكود غير المتزامن يُقرأ كالمتزامن. الأخطاء عبر try/catch.

مثال بسيط: الـ await الثلاث المرتبة في سكربت ساليم النهائي.

ما ليس كذلك: ‏sleep تحظر التطبيق كله.

رد فعل: اكتب بـ async/await افتراضيًا.

error-first / try-catch
إدارة الأخطاء حسب الشكل: الخطأ في الوسيط الأول لدوال رد الاتصال، و.catch() للوعود، وtry/catch حول await.

مثال بسيط: الـ try/catch الوحيدة التي تؤمّن عمليات ساليم الثلاث.

ما ليس كذلك: تجاهل الخطأ وترك التطبيق يتعطل بصمت.

رد فعل: دائمًا مسار خطأ صريح.

في التطبيق — عمليًا — إتقان غير المتزامن

  1. اكتبوا عملية غير متزامنة بدالة رد اتصال (مثل fs.readFile) ولاحظوا اتفاقية error-first ‏(err, data).
  2. سلسِلوا عمليتين تابعتين بدالتي رد اتصال متداخلتين: لاحظوا بداية جحيم رد الاتصال.
  3. أعيدوا الكتابة بوعود وتسلسل .then().then().catch(): لاحظوا المقروئية الأفضل.
  4. أعيدوا الكتابة أخيرًا بـ async/await (دالة async، وawait أمام العمليات، وtry/catch للأخطاء): الكود يُقرأ كالمتزامن.
تفهمون غير المتزامن وتتعاملون معه بأشكاله الثلاثة (رد الاتصال، الوعود، async/await)، وتفضّلون async/await لكود مقروء — المنعطف المركزي في Node.

نص جاهز — شرح غير المتزامن لزميل (دقيقة)

«في Node، العملية البطيئة تنطلق لكن نتيجتها تصل لاحقًا: لا تُقرأ أبدًا في السطر التالي. نقول +عندما يجهز، افعل هذا+ — بدالة رد اتصال أو .then() أو await. لهذا نكتب بـ async/await: الكود ينتظر دون حظر ويُقرأ بالترتيب.»

يُقال أمام الخطأ الكلاسيكي لـ console.log الذي يعرض undefined.

نموذج نسخ ولصق — قراءة ملف بـ async/await

const fs = require("fs");
async function lire(nom) {
  try {
    const data = await fs.promises.readFile(nom, "utf8");
    console.log(data);
    return data;
  } catch (err) {
    console.error("Echec :", err.message);
    return null;
  }
}
lire("f.txt");

عمليات تابعة

قراءة ملف، ثم استجواب بمحتواه: سلسِلوا await بالترتيب داخل try/catch واحدة — كما فعل ساليم مع config ← base ← API.

عمليات مستقلة

قراءات متعددة لا رابط بينها: أطلقوها معًا مع Promise.all للسرعة بدل انتظار كل واحدة دورها.

كود قديم بدوال رد اتصال

أمام API ‏error-first موجودة: افهموها واستعملوها كما هي للقراءة، لكن اكتبوا كل الكود الجديد بـ async/await.

خطأ صامت في الجزائر

يضيف ساليم منهجيًا catch تعرض err.message: خطأ غير متزامن غير مُدار يعطّل أو يكذب بصمت — لا await دون شبكة أمان أبدًا.

✓ ممارسات جيدة

  • ميّزوا بين «الإطلاق» و«الحصول».
  • اكتبوا بـ async/await افتراضيًا.
  • دائمًا مسار خطأ صريح.

✗ أخطاء شائعة

  • قراءة النتيجة في السطر التالي.
  • تداخل دوال رد الاتصال التابعة.
  • ‏await دون try/catch.

النقاط الأساسية

  • غير المتزامن هو المفهوم المركزي في Node: العمليات البطيئة تنطلق ونتيجتها تصل لاحقًا.
  • التطور: دوال رد الاتصال (error-first) ← خطر جحيم رد الاتصال ← الوعود (تسلسل .then/.catch) ← ‏ASYNC/AWAIT.
  • الوعد = قيمة مستقبلية (pending/fulfilled/rejected)؛ نتفاعل مع .then() (نجاح) و.catch() (خطأ).
  • ‏ASYNC/AWAIT (يُفضَّل): await تنتظر دون حظر، والكود يُقرأ كالمتزامن، والأخطاء عبر try/catch.

تحدي 48 ساعة — طبّق غدًا

خذوا النموذج أعلاه: اقرأوا ملفًا حقيقيًا بـ دالة رد اتصال، وأعيدوا الكتابة بـ الوعود، ثم بـ async/await مع try/catch. قارنوا النسخ الثلاث. تكفي 25 دقيقة.

قائمة الخروج من القسم
  • أستطيع شرح «الإطلاق» مقابل «الحصول على النتيجة».
  • أعرف متى أستعمل رد الاتصال أو الوعد أو await.
  • كتبت العملية نفسها بالأشكال الثلاثة.
تقييم ذاتي 1-5: هل أرصد خطأ النتيجة المقروءة مبكرًا؟هل كودي الجديد بـ async/await مع try/catch؟

أسئلة شائعة

لماذا يكون غير المتزامن مربكًا جدًا في البداية، وكيف نفهمه حقًا؟

يكسر الحدس «كل سطر ينتهي قبل التالي». الفخ الكلاسيكي: قراءة النتيجة في السطر التالي للاستدعاء — نحصل على undefined لأن العملية لم تنته بعد. النموذج الذهني الواجب تبنيه: التمييز بين «الإطلاق» (فوري) و«الحصول على النتيجة» (لاحقًا، عبر دالة رد اتصال أو .then() أو await). فكّروا «عندما يجهز، افعل هذا» — كطلب في مطعم: لا نبقى واقفين في المطبخ، بل نواصل ونُخدَم عندما يجهز. وما إن يحدث هذا الانفتاح، كما حدث لساليم، يصير كل Node مقروءًا.

دوال رد الاتصال أم الوعود أم async/await: أيها نستعمل اليوم؟

يجب أن تعرفوا الثلاثة لكن تكتبوا بـ async/await. دوال رد الاتصال: الشكل التاريخي، للفهم لقراءة الكود الموجود والـ API الكلاسيكية (اتفاقية error-first) — تجنّبوا تداخلها. الوعود: الأساس الحديث (async/await مبنية عليها) — للفهم، ومفيدة مباشرة للتوازي (Promise.all). async/await: أسلوبكم الافتراضي لكل كود جديد — مقروء، والأخطاء عبر try/catch. قاعدة عملية: اكتبوا بـ await، وافهموا الوعود، واعرفوا قراءة دوال رد الاتصال.

تحقّق من فهمك

ماذا تفعل await داخل دالة async؟

موارد إضافية (اختياري، للتعمّق أكثر)

اختبار المستوى 1أسئلة جديدة وأصعب للتحقق من مكتسباتك