EventEmitter هو الصنف في وحدة events في Node الذي يتيح لكائن أن يعلن أحداثًا مسماة. تسجّل مستمعًا بـ emitter.on('name', fn) وتطلقه بـ emitter.emit('name', ...args)؛ فيعمل كل مستمع لذلك الاسم، بترتيب إضافته، مع الوسائط التي مرّرتها. خوادم HTTP والتدفقات وprocess كلها EventEmitter، فتعمل هذه الدوال القليلة نفسها في كل مكان في Node.
يعمل المستمعان كلاهما بين before emit وafter emit: تستدعيهما emit بشكل متزامن ولا تعود إلا حين ينتهيان. بدّل موضعي استدعاءي shop.on فيتبدّل السطران الأوسطان أيضًا.
on وonce وoff
تُبقي on المستمع حتى تزيله، وتزيله once بعد أول استدعاء، وتزيل off (وهي نفسها removeListener) مستمعًا واحدًا. لإزالة مستمع تحتاج إلى الدالة نفسها التي أضفتها، فسمِّها بدلًا من تمرير دالة سهمية مباشرة.
يُطبع first open ever مرة واحدة، بعد Ada. وبعد إزالة logOpen لا يبقى أحد يستمع، فتعيد emit الأخيرة false؛ وكان door.removeAllListeners('open') سيزيل المستمعين كليهما في استدعاء واحد.
| الدالة | ماذا تفعل |
|---|---|
on(name, fn) وaddListener | تستدعي fn مع كل emit |
once(name, fn) | تستدعي fn مع emit التالية فقط |
prependListener(name, fn) | مثل on، لكنها تعمل قبل المستمعين الموجودين |
off(name, fn) وremoveListener | تزيل تلك الدالة |
emit(name, ...args) | تستدعي المستمعين، وتعيد true إن وُجد أي منهم |
listenerCount(name) | عدد مستمعي الحدث |
حدث error
للحدث 'error' وضع خاص. إذا أطلقته ولم يكن أحد يستمع، ترمي emit الخطأ، وفي برنامج حقيقي يؤدي الخطأ غير الملتقط إلى انهيار العملية. أضف دائمًا مستمعًا لـ 'error' إلى كل EventEmitter قد يفشل، مثل التدفقات والخوادم.
دون مستمع، ترمي emit الخطأ فيلتقطه catch؛ وبعد إضافة المستمع، تستدعيه emit نفسها ببساطة ويتابع البرنامج. أما أي اسم حدث آخر بلا مستمعين فيُتجاهل بصمت.
وراثة EventEmitter في صنفك الخاص
الطريقة المعتادة لاستخدامه هي أن يرث صنفك EventEmitter، وتستدعي super() في الباني وthis.emit حين يحدث شيء ما. عندها يشترك المستدعون في كائنك كما في أي كائن من Node.
يطبع 25% و50% و75% و100% ثم photo.png uploaded. انقل up.start() فوق سطري up.on فلا يُطبع شيء: تنطلق الأحداث قبل أن يستمع أحد.
ينطبق الفخ نفسه على الإطلاق من داخل الباني. المكتبات التي يجب أن تطلق حدثًا فورًا تغلّف الاستدعاء بـ process.nextTick، التي تعمل بعد أسطر on لدى المستدعي (راجع صفحة حلقة الأحداث).
انتظار حدث عبر events.once
تعيد events.once(emitter, name) وعدًا يتحقق بوسائط الحدث، كمصفوفة، في المرة التالية التي يُطلق فيها. فتحوّل حدثًا لمرة واحدة إلى شيء يمكنك انتظاره بـ await، وترفض الوعد إن أطلق الكائن 'error' أولًا.
يتوقف await حتى يطلق المؤقت ready، ثم تُفكك المصفوفة إلى الوسيطين. وللحدث الذي ينطلق مرات كثيرة، تعطيك events.on(emitter, name) مكرّرًا غير متزامن (async iterator) يمكنك المرور عليه بـ for await.
أخطاء شائعة
إضافة مستمع داخل دالة تعمل كثيرًا. استدعاء on في كل معالج طلب يراكم المستمعين ويستهلك الذاكرة. أضفه مرة واحدة، أو استخدم once.
إزالة دالة مكتوبة مباشرة. off('data', () => ...) لا تزيل شيئًا، لأن تلك الدالة السهمية دالة جديدة. احتفظ بمرجع إلى الدالة التي أضفتها.
توقّع أن يعمل المستمعون لاحقًا. emit متزامنة، فالمستمع البطيء يؤخر الشيفرة التي أطلقت الحدث. ولا يفيد تعليم المستمع بـ async، لأن شيفرته تبقى تعمل على الخيط نفسه حتى أول await. قسّم العمل الثقيل إلى أجزاء بينها setImmediate، أو انقله إلى خيط عامل (worker thread).
الأسئلة الشائعة
هل EventEmitter غير متزامن؟
لا. تستدعي emit كل مستمع فورًا، واحدًا تلو الآخر، وتعود حين ينتهي الأخير. يمكن للشيفرة داخل المستمع أن تبدأ عملًا غير متزامن، لكن المستمع نفسه يُستدعى بشكل متزامن.
ماذا يعني التحذير MaxListenersExceededWarning؟
يحذّر Node حين يُضاف أكثر من 10 مستمعين لحدث واحد، لأن ذلك يعني غالبًا أن مستمعًا يُضاف مع كل طلب ولا يُزال أبدًا. أزل المستمعين الزائدين، أو استدعِ emitter.setMaxListeners(n) إن كنت تحتاج فعلًا إلى المزيد.
ماذا تعيد emit؟
true إن كان للحدث مستمع واحد على الأقل، وfalse إن لم يكن أحد يستمع. ولا تعيد ما يعيده المستمعون.
ما كائنات Node.js التي هي EventEmitter؟
معظم الكائنات الأساسية في Node: خوادم HTTP، والطلبات والاستجابات، والتدفقات (streams)، والمقابس، وprocess، وواجهات readline. لهذا تشترك كلها في on وonce وoff.