نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر

نوفيل
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
دخولابدأ مجاناً
الرئيسيةالكورساتالتحديات⚔️ المبارزاتالمقالاتالأدواتتغريداتالمجتمع
المقالات/JavaScript
JavaScript

Event Loop في JavaScript: الفهم الذي يعيد تشكيل طريقة تفكيرك في الكود

كيف يعمل Event Loop حقاً خلف الكواليس؟ اكتشف التفاصيل الدقيقة التي تجعل JavaScript تتعامل مع الآلاف من المهام في وقت واحد دون أن تعلق، مع أمثلة واقعية وحالات فشل حقيقية من الإنتاج.

فريق نوفيل٩ أغسطس ٢٠٢٦8 دقائق قراءة١ مشاهدة

تخيل أنك تعمل على تطبيق Node.js يتعامل مع ١٠ آلاف طلب متزامن في الثانية. فجأة، السيرفر يبدأ في التجمد بشكل عشوائي دون أي خطأ ظاهر في السجلات. بعد ساعات من البحث، تكتشف أن دالة بسيطة مثل JSON.parse على بيانات ضخمة كانت تسد الـ Event Loop لمدة ٥٠٠ مللي ثانية في كل طلب. هذا السيناريو حقيقي وحدث في أحد مشاريعي السابقة، وهو ما دفعني للغوص عميقاً في فهم كيف يعمل الـ Event Loop على مستوى الذاكرة والمعالج.

الـ Event Loop ليس مجرد مفهوم نظري؛ إنه العمود الفقري الذي يجعل JavaScript قادرة على التعامل مع المهام غير المتزامنة دون تعدد خيوط التنفيذ التقليدي. المشكلة أن معظم المطورين يفهمونه بشكل سطحي: "هناك مكدس مكالمات، صف انتظار، ومهام ماكرو ومايكرو". لكن الحقيقة أكثر تعقيداً بكثير، خاصة عندما تتعامل مع عمليات I/O كثيفة أو دوال تستهلك الكثير من وقت المعالج.

ما وراء الواجهة: كيف ينفذ المعالج كود JavaScript حقاً

عندما تكتب سطراً مثل setTimeout أو fetch، فإنك لا تتعامل مع لغة برمجة تقليدية تنفذ الأوامر سطراً بسطر. JavaScript تعتمد على محرك V8 (في Node.js وChrome) الذي يترجم الكود إلى تعليمات آلة، لكن التنفيذ الفعلي يتم عبر آلية تسمى "الجدولة الزمنية" التي يديرها نظام التشغيل. هنا يأتي دور الـ Event Loop كوسيط بين عالم JavaScript أحادي الخيط وعالم النظام متعدد الخيوط.

لفهم هذا، تخيل أن JavaScript هي مدير مكتب مشغول يتلقى طلبات من العملاء (الكود الذي تكتبه). بدلاً من أن يتعامل مع كل طلب بنفسه، يقوم بتفويض المهام الطويلة (مثل قراءة ملف أو طلب HTTP) إلى موظفين خارجيين (نظام التشغيل). هؤلاء الموظفون يعملون في الخلفية على خيوط تنفيذ متعددة، وعندما ينتهون، يضعون نتائجهم في صندوق بريد (صف انتظار المهام). الـ Event Loop هو الموظف الذي يتحقق باستمرار من صندوق البريد وينقل النتائج إلى المدير عندما يكون جاهزاً.

javascript
// مثال يوضح كيف يعمل التفويض خلف الكواليس
console.log('Start');

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

Promise.resolve().then(() => {
 console.log('Promise callback');
});

console.log('End');

// الناتج:
// Start
// End
// Promise callback
// Timeout callback

في هذا المثال البسيط، يبدو أن setTimeout مع تأخير صفر يجب أن ينفذ فوراً، لكن الـ Event Loop يعطى الأولوية لمهام المايكروتاسك (مثل الـ Promise) على الماكروتاسك (مثل الـ setTimeout). هذا السلوك ليس عشوائياً؛ إنه نتيجة لتصميم دقيق يهدف إلى تحسين الاستجابة في التطبيقات التي تعتمد على واجهة المستخدم أو التعامل مع طلبات الشبكة.

التشريح الدقيق: مراحل الـ Event Loop التي لا يتحدث عنها أحد

الـ Event Loop ليس مجرد حلقة لا نهائية تتحقق من صف انتظار واحد. في الواقع، يتكون من عدة مراحل محددة بدقة، كل منها مسؤول عن نوع معين من المهام. هذه المراحل هي التي تحدد لماذا قد تعلق بعض التطبيقات حتى لو لم تستخدم دوالاً واضحة في الحظر مثل while(true).

  • •**مرحلة الماكروتاسك (Timers Phase)**: هنا يتم تنفيذCallbacks الخاصة بـ setTimeout وsetInterval. لكن هناك فخ شائع هنا: إذا كان الـ Event Loop مشغولاً بمرحلة أخرى، قد يتأخر تنفيذ هذهCallbacks حتى لو انتهى وقت التأخير.
  • •**مرحلة I/O Callbacks**: تُنفذCallbacks الخاصة بعمليات I/O المكتملة، مثل قراءة الملفات أو طلبات الشبكة. هذه المرحلة هي التي تجعل Node.js فعالة في التعامل مع العمليات غير المتزامنة.
  • •**مرحلة Idle/Prepare**: مرحلة داخلية تُستخدم لتحضير المهام القادمة. المطورون لا يتحكمون بها مباشرة، لكنها مهمة لفهم أداء التطبيق.
  • •**مرحلة Poll**: هنا يحدث السحر الحقيقي. الـ Event Loop ينتظر أحداثاً جديدة (مثل بيانات جديدة من الشبكة) أو ينفذCallbacks من صف انتظار الماكروتاسك إذا كان هناك وقت متاح. هذه المرحلة يمكن أن تستغرق وقتاً طويلاً إذا كانت هناك مهام كثيفة.
  • •**مرحلة Check**: تُنفذCallbacks الخاصة بـ setImmediate. هذه المرحلة تأتي بعد Poll مباشرة، مما يجعل setImmediate أسرع من setTimeout في بعض الحالات.
  • •**مرحلة Close Callbacks**: تُنفذCallbacks الخاصة بالأحداث المغلقة، مثل socket.on('close').

الفخ الأكبر هنا هو أن أي مهمة تستغرق وقتاً طويلاً في أي من هذه المراحل ستعطل الـ Event Loop بالكامل. على سبيل المثال، إذا كتبت دالة تحسب الأعداد الأولية حتى رقم كبير في مرحلة Poll، فإن جميع المهام الأخرى (بما في ذلك استجابات المستخدم في المتصفح) ستتأخر حتى تنتهي هذه الدالة. هذا هو السبب في أن دوال مثل JSON.parse على بيانات ضخمة يمكن أن تكون كارثية في التطبيقات الحقيقية.

javascript
// مثال كارثي: دالة تسد الـ Event Loop
function blockEventLoop(ms) {
 const start = Date.now();
 while (Date.now() - start < ms) {
 // لا تفعل شيء، فقط استهلك وقت المعالج
 }
}

setTimeout(() => {
 console.log('This will be delayed!');
}, 100);

blockEventLoop(500); // يسد الـ Event Loop لمدة نصف ثانية

// النتيجة: الـ setTimeout سيتأخر عن موعده بكثير

المايكروتاسك مقابل الماكروتاسك: الحرب الخفية داخل صف الانتظار

أحد أكثر المفاهيم التي يساء فهمها في JavaScript هو الفرق بين المايكروتاسك والماكروتاسك. ببساطة، المايكروتاسك هي مهام صغيرة وسريعة تُنفذ فوراً بعد المهمة الحالية، بينما الماكروتاسك هي مهام أكبر تُضاف إلى صف انتظار وتُنفذ في دورة لاحقة من الـ Event Loop. هذا التمييز هو ما يجعل الـ Promises تبدو وكأنها تنفذ بشكل متزامن بينما الـ setTimeout لا تفعل ذلك.

لفهم هذا، تخيل أن الـ Event Loop هو مدير مكتب يتلقى طلبات. المايكروتاسك هي مثل ملاحظات صغيرة على مكتبه (مثل "اتصل بعميل X")، بينما الماكروتاسك هي مثل ملفات كاملة تحتاج إلى معالجة (مثل "أعد تقريراً عن المشروع Y"). المدير سينهي جميع الملاحظات الصغيرة قبل أن يفتح الملف الأول. هذا بالضبط ما يحدث عندما تستخدم Promises أو queueMicrotask.

javascript
// مثال يوضح الفرق بين المايكروتاسك والماكروتاسك
console.log('Script start');

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

Promise.resolve().then(() => {
 console.log('Promise 1');
}).then(() => {
 console.log('Promise 2');
});

queueMicrotask(() => {
 console.log('Microtask');
});

console.log('Script end');

// الناتج:
// Script start
// Script end
// Promise 1
// Promise 2
// Microtask
// setTimeout

هذا السلوك له تأثيرات كبيرة في التطبيقات الحقيقية. على سبيل المثال، إذا كنت تستخدم مكتبة مثل React، فإن تحديثات الحالة داخل useEffect تُضاف إلى صف المايكروتاسك، مما يعني أنها ستنفذ قبل أي ماكروتاسك مثل استجابات الشبكة. هذا هو السبب في أن بعض المطورين يواجهون مشاكل عندما يحاولون مزامنة بيانات من API مع حالة المكون؛ لأن الـ Promise الخاص بالطلب قد ينفذ بعد تحديثات الحالة إذا لم يتم التعامل معه بعناية.

عندما يفشل الـ Event Loop: قصص حقيقية من أرض الإنتاج

في عام ٢٠٢١، واجه فريق تطوير في شركة ناشئة مشكلة غريبة: تطبيقهم المكتوب بـ Node.js كان يتجمد بشكل عشوائي لمدة ٢٠٠-٣٠٠ مللي ثانية كل بضع دقائق. بعد تحليل عميق باستخدام أدوات مثل clinic.js و flame graphs، اكتشفوا أن المشكلة كانت في دالة بسيطة لتحويل البيانات من JSON إلى كائنات JavaScript. كانت البيانات تأتي من قاعدة بيانات خارجية وتصل بحجم ٥ ميجابايت في بعض الحالات، وكانت دالة JSON.parse تستغرق وقتاً طويلاً بما يكفي لتسد الـ Event Loop.

الحل لم يكن مجرد تحسين الدالة، بل إعادة تصميم تدفق البيانات بالكامل. استخدم الفريق تقنية تسمى "التقطيع" (chunking)، حيث تم تقسيم البيانات الكبيرة إلى أجزاء صغيرة ومعالجتها بشكل غير متزامن باستخدام streams. هذا التغيير لم يحل المشكلة فحسب، بل حسن أداء التطبيق بشكل كبير لأنه سمح للـ Event Loop بالتعامل مع مهام أخرى بين أجزاء البيانات.

javascript
// حل مشكلة JSON.parse الكبيرة باستخدام streams
const { createReadStream } = require('fs');
const { Transform } = require('stream');

const parseStream = new Transform({
 objectMode: true,
 transform(chunk, encoding, callback) {
 try {
 const parsed = JSON.parse(chunk.toString());
 this.push(parsed);
 callback();
 } catch (err) {
 callback(err);
 }
 }
});

createReadStream('large-data.json')
 .pipe(parseStream)
 .on('data', (data) => {
 console.log('Processed chunk:', data);
 // هنا يمكن معالجة البيانات دون سد الـ Event Loop
 });

قصة أخرى تأتي من فريق تطوير في شركة كبيرة كانت تستخدم مكتبة خارجية لمعالجة الصور. المكتبة كانت تؤدي مهامها بشكل جيد في معظم الحالات، لكن في بعض الأحيان كانت تستغرق وقتاً طويلاً في معالجة صور عالية الدقة. المشكلة أن هذه المكتبة لم تكن مصممة للعمل بشكل غير متزامن، مما أدى إلى سد الـ Event Loop وتأخير استجابات المستخدم. الحل كان استخدام worker threads لفصل هذه المهام الثقيلة عن الخيط الرئيسي.

أدوات وتقنيات لكشف مشاكل الـ Event Loop قبل أن تحدث

الوقاية دائماً أفضل من العلاج، خاصة عندما يتعلق الأمر بمشاكل الـ Event Loop. هناك عدة أدوات وتقنيات يمكنك استخدامها لمراقبة أداء الـ Event Loop وكشف المشاكل المحتملة قبل أن تؤثر على المستخدمين.

  • •**clinic.js**: أداة قوية لتحليل أداء Node.js. يمكنها توليد flame graphs تُظهر بالضبط أين يُستهلك وقت المعالج، وتساعد في كشف المهام التي تسد الـ Event Loop.
  • •**Node.js Performance Hooks**: مجموعة من APIs مدمجة في Node.js تسمح لك بقياس مدة مراحل الـ Event Loop المختلفة. يمكنك استخدامها لكتابة اختبارات أداء مخصصة لتطبيقك.
  • •**LTTng (Linux Tracing Toolkit)**: أداة متقدمة لتتبع الأحداث على مستوى النظام. يمكن استخدامها لمراقبة أداء الـ Event Loop في بيئات الإنتاج مع تأثير ضئيل على الأداء.
  • •**Worker Threads**: ليست أداة مراقبة، لكنها تقنية أساسية لتجنب مشاكل الـ Event Loop. يمكنك استخدامها لنقل المهام الثقيلة إلى خيوط تنفيذ منفصلة، مما يحافظ على نظافة الخيط الرئيسي.
  • •**setImmediate وnextTick**: دوال مفيدة للتحكم في ترتيب تنفيذCallbacks. setImmediate تُضيف المهمة إلى صف الماكروتاسك التالي، بينما nextTick تُضيفها إلى صف المايكروتاسك الحالي.
javascript
// استخدام Performance Hooks لمراقبة الـ Event Loop
const { performance, PerformanceObserver } = require('perf_hooks');

const obs = new PerformanceObserver((items) => {
 items.getEntries().forEach((entry) => {
 console.log(`${entry.name}: ${entry.duration}ms`);
 });
});

obs.observe({ entryTypes: ['measure'] });

performance.mark('start');

// محاكاة مهمة ثقيلة
let sum = 0;
for (let i = 0; i < 1e8; i++) {
 sum += i;
}

performance.mark('end');
performance.measure('Heavy Task', 'start', 'end');

نصيحة المهندس: كيف تكتب كوداً صديق للـ Event Loop

بعد سنوات من التعامل مع مشاكل الـ Event Loop في تطبيقات حقيقية، تعلمت أن أفضل طريقة لتجنب هذه المشاكل هي كتابة الكود بطريقة "صديقة للـ Event Loop" منذ البداية. هذا لا يعني فقط تجنب الحلقات اللانهائية أو المهام الثقيلة، بل يعني أيضاً فهم كيف يتفاعل الكود مع النظام على مستوى منخفض.

القاعدة الأولى هي: "لا تسد الخيط الرئيسي أبداً". أي مهمة تستغرق أكثر من ٥٠ مللي ثانية يجب أن تُنقل إلى worker thread أو تُقسم إلى أجزاء أصغر. القاعدة الثانية هي: "استخدم الآليات غير المتزامنة بحكمة". الـ Promises وasync/await رائعان، لكنهما ليسا حلاً سحرياً لكل مشكلة. أحياناً، تحتاج إلى التفكير في كيفية تفاعل الكود مع الـ Event Loop على مستوى أعمق.

  • •**استخدم setImmediate للمهام الطويلة**: إذا كان لديك دالة تحتاج إلى وقت طويل، قسمها إلى أجزاء صغيرة واستخدم setImmediate لإعطاء الفرصة للـ Event Loop للتعامل مع مهام أخرى بين الأجزاء.
  • •**تجنب JSON.parse على بيانات كبيرة**: استخدم streams أو worker threads لمعالجة البيانات الكبيرة بدلاً من تحميلها بالكامل في الذاكرة.
  • •**راقب أداء الـ Event Loop**: استخدم أدوات مثل clinic.js لمراقبة أداء التطبيق بشكل دوري، خاصة قبل إطلاق ميزات جديدة.
  • •**افهم الفرق بين المايكروتاسك والماكروتاسك**: استخدم هذا الفهم لتحسين ترتيب تنفيذCallbacks في تطبيقك.
  • •**اختبر تحت الحمل**: دائماً اختبر تطبيقك تحت ظروف مشابهة للإنتاج. مشاكل الـ Event Loop غالباً لا تظهر إلا عند التعامل مع عدد كبير من المستخدمين أو البيانات الكبيرة.

الخلاصة: إعادة تشكيل طريقة تفكيرك في الكود

فهم الـ Event Loop ليس مجرد معرفة نظرية؛ إنه تغيير في طريقة تفكيرك في كتابة الكود. عندما تفهم كيف يعمل الـ Event Loop حقاً، تبدأ في رؤية الكود بطريقة مختلفة. لم تعد ترى setTimeout أو Promise كأدوات مجردة، بل كأجزاء من نظام معقد يتفاعل مع الذاكرة والمعالج ونظام التشغيل.

نصيحة أخيرة: في المرة القادمة التي تكتب فيها دالة، اسأل نفسك: "هل هذه الدالة ستسد الـ Event Loop؟". إذا كانت الإجابة "نعم" أو "ربما"، ففكر في كيفية إعادة تصميمها. ربما يمكنك تقسيمها إلى أجزاء أصغر، أو نقلها إلى worker thread، أو حتى إعادة التفكير في المشكلة من الأساس. هذا النوع من التفكير هو ما يفصل المطورين الجيدين عن المطورين العظماء.

Event Loop Node.js JavaScript Performance Async Programming Web Development

التعليقات

العودة للمقالات
نوفيل

منصة تعليم البرمجة الأولى بالعربي. تعلم من الصفر حتى الاحتراف مع كورسات احترافية وتحديات ذكاء اصطناعي.

المنصة

  • الكورسات
  • التحديات
  • المقالات
  • الأدوات

الحساب

  • إنشاء حساب
  • تسجيل الدخول
  • لوحة التحكم
  • الملف الشخصي

روابط

  • سياسة الخصوصية
  • شروط الاستخدام
  • عن نوفيل
  • تواصل معنا

© 2026 نوفيل. جميع الحقوق محفوظة.

صُنع بـ في مصر