شرح حلقة أحداث Node.js للمبتدئين

Backend2026-09-14TryQuickToolBox

ربما سمعت أن Node.js أحادي الخيط ويستخدم حلقة أحداث للتعامل مع آلاف الاتصالات المتزامنة. لكن ماذا يعني ذلك فعليًا؟ إذا كنت جديدًا في Node.js، قد تبدو حلقة الأحداث وكأنها صندوق أسود غامض. هذه المقالة تشرحها بلغة بسيطة، حتى تتمكن من كتابة كود Node.js أسرع وأفضل.

لماذا توجد حلقة الأحداث

خوادم الويب التقليدية (مثل Apache مع PHP) تنشئ خيطًا أو عملية جديدة لكل طلب. هذا يعمل، لكنه لا يتوسع جيدًا عندما يكون لديك العديد من الاتصالات المتزامنة—كل خيط يستهلك الذاكرة والمعالج. Node.js تتبع نهجًا مختلفًا: تشغل JavaScript في خيط واحد وتستخدم حلقة أحداث للتعامل مع عمليات الإدخال/الإخراج بشكل غير متزامن. هذا يعني أن الكود الخاص بك لا ينتظر استعلام قاعدة بيانات أو قراءة ملف حتى ينتهي؛ بل يستمر في تنفيذ مهام أخرى ويعود عندما تكون النتيجة جاهزة.

المكونات الأساسية

قبل الغوص في حلقة الأحداث، دعنا نوضح العناصر الرئيسية:

كيف تعمل حلقة الأحداث: مثال بسيط

خذ بعين الاعتبار هذا الكود:

console.log('Start');

setTimeout(() => {
  console.log('Timeout');
}, 0);

console.log('End');

ما هو الناتج؟ إذا خمنت Start، End، Timeout، فأنت محق. على الرغم من أن المهلة 0 مللي ثانية، لا يتم تنفيذ الاستدعاء فورًا. إليك ما يحدث:

  1. يتم دفع console.log('Start') إلى مكدس الاستدعاءات، وتنفيذه، وإخراجه.
  2. يتم استدعاء setTimeout. تسجل Node.js المؤقت وتضبطه لتنتهي بعد 0 مللي ثانية. يتم تخزين الاستدعاء، وتعود الدالة.
  3. يتم دفع console.log('End')، وتنفيذه، وإخراجه.
  4. الآن مكدس الاستدعاءات فارغ. تتحقق حلقة الأحداث من مرحلة المؤقتات وترى أن المؤقت قد انتهى. تنقل الاستدعاء إلى مكدس الاستدعاءات، الذي يسجل Timeout.

هذا يوضح الطبيعة غير المحجوبة: المؤقت لا يحجب بقية الكود.

مراحل حلقة الأحداث

تعالج حلقة الأحداث الاستدعاءات في عدة مراحل، لكل منها غرض محدد. فهم هذه المراحل يساعدك على توقع ترتيب التنفيذ.

المرحلة الوصف
المؤقتات تنفذ الاستدعاءات المجدولة بواسطة setTimeout() و setInterval().
الاستدعاءات المعلقة تنفذ استدعاءات الإدخال/الإخراج المؤجلة إلى التكرار التالي للحلقة.
الخامل، التحضير تستخدم داخليًا بواسطة Node.js.
الاستطلاع يسترد أحداث الإدخال/الإخراج الجديدة؛ ينفذ الاستدعاءات المتعلقة بالإدخال/الإخراج (جميعها تقريبًا باستثناء المؤقتات، setImmediate، واستدعاءات الإغلاق).
التحقق ينفذ الاستدعاءات المجدولة بواسطة setImmediate().
استدعاءات الإغلاق ينفذ استدعاءات الإغلاق، مثل socket.on('close', ...).

بين كل مرحلة، تتحقق Node.js من المهام الدقيقة: استدعاءات process.nextTick() و Promises. هذه لها أولوية أعلى ويتم تنفيذها فورًا بعد اكتمال العملية الحالية، قبل الانتقال إلى المرحلة التالية.

المهام الدقيقة: nextTick و Promises

المهام الدقيقة ليست جزءًا من مراحل حلقة الأحداث؛ يتم معالجتها بعد كل مرحلة وبعد كل استدعاء. هذا يجعلها تعمل قبل المؤقتات واستدعاءات الإدخال/الإخراج. على سبيل المثال:

setTimeout(() => console.log('Timeout'), 0);
Promise.resolve().then(() => console.log('Promise'));
process.nextTick(() => console.log('nextTick'));
console.log('Sync');

الناتج: Sync، nextTick، Promise، Timeout. يتم تشغيل الكود المتزامن أولاً، ثم المهام الدقيقة (nextTick قبل Promises)، ثم المؤقت.

تحذير: استدعاءات process.nextTick() المتكررة يمكن أن تجوّع حلقة الأحداث، مما يمنع حدوث الإدخال/الإخراج. استخدم setImmediate() للعمليات المتكررة.

setImmediate مقابل setTimeout

تم تصميم setImmediate() لتنفيذ استدعاء فورًا بعد اكتمال مرحلة الاستطلاع الحالية. في المقابل، setTimeout() مع 0 مللي ثانية ينتظر مرحلة المؤقت التالية. يمكن أن يختلف الترتيب بينهما حسب السياق، ولكن داخل استدعاء إدخال/إخراج، يعمل setImmediate() دائمًا أولاً.

لماذا يهم هذا لكودك

فهم حلقة الأحداث يساعدك على تجنب المزالق الشائعة:

مثال عملي: قراءة ملف غير محجوبة

إليك كيف تمكن حلقة الأحداث الإدخال/الإخراج غير المحجوب:

const fs = require('fs');

console.log('Before read');

fs.readFile('large-file.txt', 'utf8', (err, data) => {
  if (err) throw err;
  console.log('File read complete');
});

console.log('After read');

الناتج: Before read، After read، ثم File read complete. تحدث قراءة الملف في الخلفية، ويتم وضع الاستدعاء في قائمة الانتظار عند الانتهاء. وفي الوقت نفسه، يمكن تشغيل كود آخر.

الأسئلة الشائعة

هل Node.js أحادية الخيط حقًا؟

تشغل Node.js JavaScript في خيط واحد، لكنها تستخدم خيوطًا متعددة في مجموعة خيوط libuv لإدخال/إخراج الملفات، وDNS، وعمليات أخرى. لذا فهي ليست أحادية الخيط تمامًا تحت الغطاء.

ما الفرق بين مكدس الاستدعاءات وحلقة الأحداث؟

مكدس الاستدعاءات ينفذ الكود المتزامن. حلقة الأحداث تدير الاستدعاءات غير المتزامنة، وتنقلها من قوائم الانتظار إلى مكدس الاستدعاءات عندما يكون فارغًا.

هل يمكنني إنشاء حلقات أحداث متعددة؟

لا، كل عملية Node.js لها حلقة أحداث واحدة. ومع ذلك، يمكنك استخدام خيوط العمل لتشغيل خيوط JavaScript منفصلة، لكل منها حلقة الأحداث الخاصة بها.

إذا كنت تعمل مع سجلات الخادم لتصحيح مشكلات الأداء، جرب محلل سجلات Nginx الخاص بنا لتحليل بيانات السجل وتصورها بسرعة.