الفرق بين المراجعتين لصفحة: «Node.js/events»

من موسوعة حسوب
من مساهمات هبة فريد
 
طلا ملخص تعديل
سطر 1: سطر 1:
= الأحداث =
<noinclude>{{DISPLAYTITLE: الأحداث في Node.js}}</noinclude>
الثبات: مستقر
الثبات: مستقر



مراجعة 12:50، 30 يوليو 2018

الثبات: مستقر

الكثير من أساس الواجهة البرمجية Node.js مبني حول بنية توجهها أحداث متميزة غير متزامنة حيث تطلق أنواع معينة من الكائنات (تُسمى "مطلقات" [emitters]) أحداث معينة تستدعي كائنات Function ("منصتات" [listeners]).

على سبيل المثال: يطلق كائن net.server حدثًا كلما اتصل نظير بالخادم مثال آخر لكائن fs.ReadStream يطلق حدثًا عندما يُفتح ملف ما؛ ويطلق كائن stream حدثًا كلما أتيحت بيانات للقراءة.

كل الكائنات التي تطلق أحداثًا هي من صنف مطلق الحدث EventEmitter. حيث تكشف عن الدالة eventEmitter.on()‎ التي تتيح إلحاق دالة أو أكثر مع الأحداث المعينة التي يطلقها هذا الكائن. عادة ما تكون أسماء الأحداث ذات أحرف كبيرة في بدايتها (مثل ReadStream) لكن يمكن استخدام أي معرف صالح في JavaScript.

عندما يطلق كائن مطلق الحدث EventEmitter حدثًا، تُستدعى كل الدوال الملحقة بذلك الحدث تزامنيًا (synchronously). مع تجاهل أي قيم تعود من دوال المنصتات المستدعاة.

المثال التالي يعرض حالة مطلق حدث EventEmitter بسيط مع منصت واحد. حيث نستخدم التابع eventEmitter.on()‎ لتسجيل المنصتات، والتابع eventEmitter.emit()‎ لإطلاق الحدث.

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
 console.log('an event occurred!');
});
myEmitter.emit('event');

تمرير الوسائط وقيمة this إلى المنصتات

يتيح التابع eventEmitter.emit()‎ مجموعة اختيارية من الوسائط لتمريرها إلى دوال المنصتات. لاحظ عند استدعاء دالة منصتة، ستشير قيمة المعامل this إلى نسخة الكائن EventEmitter الذي ارتبط به المنصت.

const myEmitter = new MyEmitter();
myEmitter.on('event', function(a, b) {
 console.log(a, b, this, this === myEmitter);
 // يطبع
 //   a b MyEmitter {
 //     domain: null,
 //     _events: { event: [Function] },
 //     _eventsCount: 1,
 //     _maxListeners: undefined } true
});
myEmitter.emit('event', 'a', 'b');

يمكن استخدام الدوال السهمية كمنصتات، لكن في هذه الحالة، لا تعود قيمة this تدل على كائن مطلق الحدث EventEmitter:

const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
 console.log(a, b, this);
 // يطبع: a b {}
});
myEmitter.emit('event', 'a', 'b');

الاستدعاء المتزامن والاستدعاء غير المتزامن

يستدعي مطلق الحدث EventEmitter جميع المنصتات تزامنيًا حسب ترتيب تسجيلها. من المهم الانتباه إلى الترتيب المناسب للأحداث لتجنب الظروف النادرة أو الأخطاء المنطقية. يمكن تحويل دوال الإنصات إلى الوضع غير المتزامن عند الضرورة باستخدام التوابع setImmediate()‎ أو process.nextTick()‎:

const myEmitter = new MyEmitter();
myEmitter.on('event', (a, b) => {
 setImmediate(() => {
   console.log('هذا الحدث غير متزامن');
 });
});
myEmitter.emit('event', 'a', 'b');

تنفيذ الأحداث مرة واحدة فقط

عند تسجيل منصت بطريقة eventEmitter.on()‎، يُستدعى المنصت في كل مرة يُطلق فيها الحدث المعني.

const myEmitter = new MyEmitter();
let m = 0;
myEmitter.on('event', () => {
 console.log(++m);
});
myEmitter.emit('event');
// يطبع: 1
myEmitter.emit('event');
// يطبع: 2

لكن يمكنك تنفيذ استدعاء المنصت مرة واحدة على الأكثر عند إطلاق حدث معين باستخدام التابع eventEmitter.once()‎. بالتالي عندما يُطلق الحدث، يُزال تسجيل المنصت ثم يُستدعى.

const myEmitter = new MyEmitter();
let m = 0;
myEmitter.once('event', () => {
 console.log(++m);
});
myEmitter.emit('event');
// Prints: 1
myEmitter.emit('event');
// Ignored

أحداث الأخطاء

عادة ما يُطلق الحدث "error" "خطأ" عند حدوث خطأ ما في كائن مطلق الحدث EventEmitter. وهذه حالات خاصة في Node.js.

إذا لم يكن لدى مطلق الحدث EventEmitter منصتٌ واحدٌ مسجلًا على الأقل للحدث "error"، وأُطلق الحدث "error". فستطبع عملية Node.js تتبُّعًا تكديسيًا ثم تخرج العملية (exit).

const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

للحماية من انهيار عمل Node.js يمكن استخدام الوحدة domain (لكن لا يُنصح بهذا إذ إنَّ الوحدة domain قد أُهملت). أفضل ممارسة في هذه الحالة، هي إضافة منصتات إلى أحداث "error" دائمًا.

const myEmitter = new MyEmitter();
myEmitter.on('error', (err) => {
 console.error('whoops! there was an error');
});
myEmitter.emit('error', new Error('whoops!'));
// يطبع: whoops! there was an error

الصنف: EventEmitter

أُضيف في الإصدار 0.1.26

تحدد وتكشف وحدة events الصنف EventEmitter:

const EventEmitter = require('events');

إذ تُطلِق جميع مطلقات الأحداث EventEmitter الحدث "newListener" مع إضافة منصت جديد والحدث "removeListener" عند إزالة منصت موجود.

الحدث: 'newListener'

أُضيف في الإصدار 0.1.26

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة الحدث.

يطلق الصنف EventEmitter الحدث 'newListener' الخاص به قبل إضافة منصت جديد إلى قائمة المنصتات الداخلية التابعة له.

تُمرر المنصتات المسجلة لحدث 'newListener' اسم الحدث وإشارة إلى المنصت المضاف.

تشكل حقيقة تشغيل الحدث قبل إضافة المنصت أثرًا جانبيًا خفيًا ومهمًا: ستضاف أية منصتات إضافية مسجلة لنفس الاسم داخل استدعاء 'newListener' قبل إضافة المنصت المعني.

const myEmitter = new MyEmitter();
// قم بهذا مرة واحدة فقط حتى لا ندخل في حلقة لا متناهية
myEmitter.once('newListener', (event, listener) => {
 if (event === 'event') {
   // أضف منصتًا جديدًا في المقدمة
   myEmitter.on('event', () => {
     console.log('B');
   });
 }
});
myEmitter.on('event', () => {
 console.log('A');
});
myEmitter.emit('event');
// يطبع:
//   B
//   A

الحدث: 'removeListener'

سجل التغييرات

الإصدار التغييرات
6.1.0,

4.7.0

ينتج الوسيط listener الآن دالة المنصت الأصلية، بالنسبة للمنصات المرفقة باستخدام الدالة ‎.once()‎.
0.9.3 أضيفت في الإصدار 0.9.3.
  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة الحدث.

يُطلق حدث 'removeListener' بعد إزالة المنصت.

EventEmitter.listenerCount(emitter, eventName)‎

أضيف هذا التابع في الإصدار 0.9.12 وأهمل في الإصدار 4.0.0. درجة الاستقرار 0 - مهملة ويُنصَح باستخدام التابع emitter.listenerCount()‎ بدلًا منه.

تابع يعيد عدد المنصتات لحدث معين eventName المسجلة على مُطلِق emitter المعطي.

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {});
myEmitter.on('event', () => {});
console.log(EventEmitter.listenerCount(myEmitter, 'event'));
// تطبع: 2

EventEmitter.defaultMaxListeners

أضيفت في الإصدار: 0.11.2.

افتراضيًا، يمكن تسجيل حد أقصى 10 منصتات في الحدث الفردي. لكن يمكن تغيير هذا الحد لكائنات مطلق الحدث EventEmitter باستخدام التابع emitter.setMaxListeners(n)‎. لتغيير الحد الافتراضي لجميع كائنات EventEmitter، يمكنك استخدام خاصية EventEmitter.defaultMaxListeners. يجب أن تكون القيمة عددًا موجبًا، وإلا يطلق البرنامج الخطأ TypeError.

انتبه عند تحديد EventEmitter.defaultMaxListeners لأن هذا التغيير يؤثر على جميع كائنات مطلق الحدث EventEmitter، بما فيها الموجودة بالفعل قبل التغيير. على كل حال، لا يزال لاستدعاء التابع emitter.setMaxListeners(n)‎ أولويةٌ على EventEmitter.defaultMaxListeners.

لاحظ أن هذا ليس حدًا حاسمًا. حيث يتيح كائن مطلق الحدث EventEmitter إضافة المزيد من المنصتات لكنه سيطلق تحذيرًا مفاده أن هناك "تسريب محتمل في ذاكرة EventEmitter". يمكن استخدام التوابع emitter.getMaxListeners()‎ و emitter.setMaxListeners()‎ لأي نسخة من EventEmitter لتجنب هذا التحذير مؤقتًا:

emitter.setMaxListeners(emitter.getMaxListeners() + 1);
emitter.once('event', () => {
 // نص برمجي
 emitter.setMaxListeners(Math.max(emitter.getMaxListeners() - 1, 0));
});

يمكنك استخدام خيار سطر الأوامر ‎--trace-warnings لعرض التتبع التكديسي لمثل هذه التحذيرات.

أيضًا يمكنك التحقق من التحذير المطلق من خلال process.on('warning')‎ مع خيارات emitter، و type و count، التي تشير إلى كائن مطلق الحدث EventEmitter، واسم الحدث وعدد المنصتات المرفقة، بالترتيب. اسم خاصية name هو "MaxListenersExceededWarning" لحدث التحذير.

emitter.addListener(eventName, listener)‎

أضيف في الإصدار: 0.1.26.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة الحدث.

اسم بديل للتابع emitter.on(eventName, listener)‎.

emitter.emit(eventName[, ...args])‎

أضيف في الإصدار: 0.1.26

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • ...args: أي نوع من البيانات.
  • القيمة المعادة: <boolean>.

تستدعي بشكل متزامن كل من المنصتات المسجلة للحدث المعني eventName، بترتيب تسجيلها، مع تمرير الوسائط الموفّرة لكل منها.

يعيد هذا التابع قيمةً صحيحةً true إذا كانت هناك منصتات مرتبطة بالحدث، أو القيمة false إذا لم تكن كذلك.

emitter.eventNames()‎

أضيف في الإصدار: 6.0.0.

  • القيمة المعادة: <Array>

يعيد التابع مصفوفةً فيها قائمة الأحداث المُسجّلة. يمكن أن تكون القيم في المصفوفة نصوصًا أو رموزًا (من الكائن Symbol).

const EventEmitter = require('events');
const myEE = new EventEmitter();
myEE.on('foo', () => {});
myEE.on('bar', () => {});
const sym = Symbol('symbol');
myEE.on(sym, () => {});
console.log(myEE.eventNames());
// [ 'foo', 'bar', Symbol(symbol) ]

emitter.getMaxListeners()‎

أضيف في الإصدار: 1.0.0.

  • القيمة المعادة: <integer>.

إعادة قيمة الحد الأقصى لعدد المنصتات لكائن EventEmitter الذي يمكن تحديده إما باستخدام التابعemitter.setMaxListeners(n) ‎ أو أخذ القيمة الافتراضية من EventEmitter.defaultMaxListeners.

emitter.listenerCount(eventName)‎

أضيف في الإصدار 3.2.0.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • القيمة المعادة: <integer>.

إعادة عدد المنصتات التي تستمع إلى وقوع الحدث المسمى eventName.

emitter.listeners(eventName)‎

سجل التغييرات

الإصدار التغييرات
7.0.0 بالنسبة للمنصتات المرفقة بواسطة ‎.once()‎ تعيد هذه الطريقة المنصتات الأصلية بدلًا من الدوال المُغلِّفة الآن.
0.1.26 أضيفت في الإصدار 0.1.26.
  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • القيمة المعادة: <Function[]‎>.

إعادة نسخة من مصفوفة المنصتات للحدث المعني eventName.

server.on('connection', (stream) => {
 console.log('someone connected!');
});
console.log(util.inspect(server.listeners('connection')));
// تطبع: [ [Function] ]

emitter.off(eventName, listener)‎

أضيف في الإصدار 10.0.0.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة رد النداء (الاستدعاء).
  • القيمة المعادة: <EventEmitter>

اسم بديل للتابع emitter.removeListener()‎.

emitter.on(eventName, listener)‎

أضيف في الإصدار 0.1.101.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة رد النداء (الاستدعاء).
  • القيمة المعادة: <EventEmitter>

تضيف دالة المنصت listener إلى نهاية مصفوفة المنصتات للحدث المعني eventName. لاحظ أن الطريقة لا تتفحص ما إذا كان المنصت قد أُضيف بالفعل أم لا. بالتالي عند الاستدعاء المتكرر بنفس مجموعة الوسائط eventName وlistener، يُضاف ويستدعى المنصت في كل مرة.

server.on('connection', (stream) => {
 console.log('someone connected!');
});

تعيد إشارةً إلى كائن EventEmitter، بحيث يمكن ربط الاستدعاءات كسلسلة (chain). افتراضيًا، تُستدعى منصتات الحدث بنفس ترتيب تسجيلها. ولكن يمكنك استخدام التابع emitter.prependListener()‎ لإضافة منصت الحدث إلى بداية مصفوفة المنصتات.

const myEE = new EventEmitter();
myEE.on('foo', () => console.log('a'));
myEE.prependListener('foo', () => console.log('b'));
myEE.emit('foo');
// تطبع:
//   b
//   a

emitter.once(eventName, listener)‎

أضيف في الإصدار: 0.3.0.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة رد النداء (callback).
  • القيمة المعادة: <EventEmitter>

تضيف دالة listener منصتًا لمرة واحدة إلى الحدث المعني eventName. بحيث يُلغى المنصت عند وقوع الحدث eventName ثم يُستدعى.

server.once('connection', (stream) => {
 console.log('Ah, we have our first user!');
});

تعود بإشارة إلى كائن EventEmitter، بحيث يمكن ربط الاستدعاءات كسلسلة (chain). افتراضيًا، تُستدعى منصتات الحدث بنفس ترتيب تسجيلها. ولكن يمكنك استخدام التابع emitter.prependOnceListener()‎ لإضافة منصت الحدث إلى بداية مصفوفة المنصتات.

const myEE = new EventEmitter();
myEE.once('foo', () => console.log('a'));
myEE.prependOnceListener('foo', () => console.log('b'));
myEE.emit('foo');
// تطبع:
//   b
//   a

emitter.prependListener(eventName, listener)‎

أضيف في الإصدار: 6.0.0.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة رد النداء (الاستدعاء).
  • القيمة المعادة: <EventEmitter>

تضيف دالة المنصت listener إلى بداية مصفوفة المنصتات للحدث المعني eventName. لاحظ أن الطريقة لا تتفحص ما إذا كان المنصت قد أُضيف بالفعل أم لا. بالتالي عند الاستدعاء المتكرر بنفس مجموعة الوسائط eventName وlistener، يُضاف ويستدعى المنصت في كل مرة.

server.prependListener('connection', (stream) => {
 console.log('someone connected!');
});

تعيد إشارةً إلى كائن EventEmitter، بحيث يمكن ربط الاستدعاءات كسلسلة (chain).

emitter.prependOnceListener(eventName, listener)‎

أضيف في الإصدار 6.0.0.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة رد النداء (الاستدعاء).
  • القيمة المعادة: <EventEmitter>

تضيف دالة listener منصت لمرة واحدة إلى بداية مصفوفة منصتات الحدث المعني eventName. بحيث يُلغى المنصت عند إطلاق الحدث eventName ثم يُستدعى بعدئذٍ.

server.prependOnceListener('connection', (stream) => {
 console.log('Ah, we have our first user!');
});

يعيد إشارة إلى كائن EventEmitter، بحيث يمكن ربط الاستدعاءات.

emitter.removeAllListeners([eventName])‎

أضيف في الإصدار 0.1.26.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • القيمة المعادة: <EventEmitter>

تزيل جميع المنصتات، أو تلك المنصتات المخصصة للحدث المعني eventName

لا يُنصح بإزالة المنصتات التي أُضيفت في مكان آخر من النص البرمجي، خاصة عند إنشا  كائن EventEmitter من قبل مكون أو وحدة أخرى (مثل المقابس [socketsٍ أو مجاري الملفات [file streams]).

يعيد إشارة إلى كائن EventEmitter، بحيث يمكن ربط الاستدعاءات كسلسلة (chain).

emitter.removeListener(eventName, listener)‎

أضيف في الإصدار: 0.1.26

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • listener <Function>‎: دالة رد النداء (الاستدعاء).
  • القيمة المعادة: <EventEmitter>

يزيل المنصت المعني listener من مصفوفة المنصتات للحدث eventName.

const callback = (stream) => {
 console.log('someone connected!');
};
server.on('connection', callback);
// ...
server.removeListener('connection', callback);

يزيل هذا التابع ظهور واحد على الأكثر للمنصت من قائمة المنصتات، بالتالي إذا أضيف منصت عدة مرات إلى مصفوفة منصتات الحدث المعني eventName، فسيتطلب الأمر استدعاء التابع removeListener()‎ عدة مرات لإزالة كل مرة ظهور. لاحظ أنه عند إطلاق الحدث، ستستدعى المنصتات المرفقة به بالترتيب في وقت الإطلاق. بالتالي أي استدعاء للتابع removeListener()‎ أو removeAllListeners()‎ بعد الإطلاق وقبل الانتهاء من تنفيذ المنصت الأخير لن يزيلها من الحدث الجاري إطلاقه emit()‎. أما الأحداث التالية فستتصرف كما هو متوقع.

const myEmitter = new MyEmitter();
const callbackA = () => {
 console.log('A');
 myEmitter.removeListener('event', callbackB);
};
const callbackB = () => {
 console.log('B');
};
myEmitter.on('event', callbackA);
myEmitter.on('event', callbackB);
// يزيل كائن‎  ‏callbackA المنصت callbackB لكن سيُستدعى مع ذلك.
// مصفوفة المنصتات الداخلية في وقت الإطلاق [callbackA, callbackB]
myEmitter.emit('event');
// تطبع:
//   A
//   B
// أزيل منصت callbackB الآن.
// مصفوفة المنصتات الداخلية [callbackA]
myEmitter.emit('event');
// تطبع:
//   A

لأن المنصتات تُدار بقائمة داخلية، فإن استدعاء هذه الطريقة سيغير من فهرس مكان أي منصت مسجل بعد المنصت المحذوف. هذا لا يغير في ترتيب استدعاء المنصتات، ولكنه يعني أن أي نسخة من مصفوفة المنصتات التي تعيدها الطريقة emitter.listeners()‎ يجب تحديثها.

يعيد هذا التابع إشارة إلى كائن EventEmitter، بحيث يمكن ربط الاستدعاءات كسلسلة (chain).

emitter.setMaxListeners(n)‎

أضيف في الإصدار: 0.3.5.

  • n <integer>‎
  • القيمة المعادة: <EventEmitter>

يطبع كائنات EventEmitter تحذيرًا افتراضيًا عندما يتجاوز عدد المنصتات المضافة إلى حدث معين 10 منصتات. هذه قيمة افتراضية مفيدة في العثور على تسريبات الذاكرة. لكن بالطبع، لا يجب أن تكون جميع الأحداث محددة فقط بعشر منصتات. وهنا يظهر التابع emitter.setMaxListeners()‎ الذي يتيح تعديل الحد الأقصى لكائن EventEmitter معين. يمكنك وضع قيمة Infinity (أو 0) لتحديد عدد لا نهائي من المنصتات.

يعيد التابع إشارة إلى كائن EventEmitter، بحيث يمكن ربط الاستدعاءات كسلسلة (chain).

emitter.rawListeners(eventName)‎

أضيف في الإصدار 9.4.0.

  • eventName <string> | <symbol>‎: اسم الحدث المُنصَت إليه.
  • القيمة المعادة: <Function[]‎>.

إعادة نسخة من مصفوفة منصتات الحدث المعني eventName، شاملة أي مغلفات (wrappers، مثل تلك المنشأة من التابع ‎.once()‎).

const emitter = new EventEmitter();
emitter.once('log', () => console.log('log once'));
// تعود بمصفوفة جديدة لدالة ‘onceWrapper’ مع خاصية
// المنصت listener التي تحتوي رابط المنصت الأصلي بالأعلى
const listeners = emitter.rawListeners('log');
const logFnWrapper = listeners[0];
// ترمي عبارة “log once” ولا تفك حدث ‘once’
logFnWrapper.listener();
// ترمي عبارة “log once” وتزيل المنصت
logFnWrapper();
emitter.on('log', () => console.log('log persistently'));
// تعود بمصفوفة جديدة لدالة فردية مربوطة بدالة ‘.on()’ المذكورة بالأعلى
const newListeners = emitter.rawListeners('log');
// ترمي عبارة ‘log persistenlty’ مرتين
newListeners[0]();
emitter.emit('log');

مصادر

  • صفحة Events في توثيق Node.js الرسمي.