ماژول Events در Node.js: معماری رویدادمحور

بخش بزرگی از API اصلی Node.js بر پایهٔ معماری رویدادمحور و غیرهمزمان ساخته شده است. در این معماری، اشیائی به نام EventEmitter رویدادهایی را منتشر می‌کنند و توابعی به نام Listener به این رویدادها واکنش نشان می‌دهند. این الگو امکان مدیریت کارآمد عملیات‌های ورودی/خروجی و جریان داده‌ها را فراهم می‌کند.

EventEmitteremit and ononce and prependListenererror eventscaptureRejections

~7 دقیقه مطالعه • بروزرسانی ۶ دی ۱۴۰۴

1. EventEmitter چیست؟


تمام اشیائی که رویداد منتشر می‌کنند نمونه‌ای از کلاس EventEmitter هستند. این کلاس متدهایی مانند on() برای ثبت Listener و emit() برای انتشار رویدادها ارائه می‌دهد.


2. نمونهٔ ساده


const EventEmitter = require('node:events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();

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

3. ارسال آرگومان‌ها و this


متد emit() می‌تواند آرگومان‌هایی به Listenerها ارسال کند. در Listenerهای معمولی، this به نمونهٔ EventEmitter اشاره دارد. در Listenerهای نوشته‌شده با Arrow Function، this به EventEmitter اشاره نمی‌کند.


4. همزمانی و غیرهمزمانی


تمام Listenerها به‌صورت همزمان و به‌ترتیب ثبت‌شدن اجرا می‌شوند. برای تغییر به حالت غیرهمزمان می‌توان از setImmediate() یا process.nextTick() استفاده کرد.


5. اجرای یک‌بار


با استفاده از once() می‌توان Listenerهایی ثبت کرد که فقط یک‌بار اجرا شوند و سپس حذف شوند.


6. مدیریت خطاها


اگر یک EventEmitter خطایی منتشر کند و هیچ Listener برای رویداد 'error' ثبت نشده باشد، فرآیند Node.js کرش خواهد کرد. بنابراین همیشه باید Listener برای خطاها ثبت شود.


myEmitter.on('error', (err) => {
  console.error('Error occurred:', err);
});

7. captureRejections


اگر Listenerها به‌صورت async تعریف شوند، ممکن است Promiseهای ردشده مدیریت نشوند. گزینهٔ captureRejections این رفتار را تغییر داده و خطاها را به رویداد 'error' یا متد Symbol.for('nodejs.rejection') هدایت می‌کند.


8. رویدادهای خاص EventEmitter


  • 'newListener': قبل از اضافه‌شدن Listener جدید منتشر می‌شود.
  • 'removeListener': بعد از حذف Listener منتشر می‌شود.

9. متدهای مهم EventEmitter


  • on(): ثبت Listener.
  • once(): ثبت Listener یک‌بار مصرف.
  • emit(): انتشار رویداد.
  • listeners(): دریافت لیست Listenerها.
  • eventNames(): دریافت نام رویدادهای ثبت‌شده.
  • setMaxListeners(): تنظیم حداکثر تعداد Listenerها.
  • prependListener(): افزودن Listener در ابتدای لیست.

نتیجه‌گیری


ماژول Events در Node.js قلب معماری رویدادمحور این پلتفرم است. با استفاده از EventEmitter و متدهای آن، توسعه‌دهندگان می‌توانند جریان‌های دادهٔ پیچیده و عملیات‌های غیرهمزمان را به‌صورت پایدار و قابل‌مدیریت پیاده‌سازی کنند.


1. prependListener()


این متد یک Listener جدید را در ابتدای آرایهٔ Listenerهای یک رویداد اضافه می‌کند.


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

2. prependOnceListener()


مشابه prependListener() است اما Listener فقط یک‌بار اجرا می‌شود و سپس حذف می‌گردد.


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

3. removeListener() و removeAllListeners()


  • removeListener(eventName, listener): حذف یک Listener مشخص.
  • removeAllListeners([eventName]): حذف همهٔ Listenerها یا Listenerهای یک رویداد خاص.

توجه: حذف Listenerهایی که توسط ماژول‌های دیگر اضافه شده‌اند می‌تواند باعث مشکلات جدی شود.


4. setMaxListeners() و defaultMaxListeners


به‌طور پیش‌فرض، اگر بیش از 10 Listener برای یک رویداد ثبت شود، Node.js هشدار Memory Leak صادر می‌کند. با setMaxListeners(n) می‌توان این محدودیت را تغییر داد. همچنین events.defaultMaxListeners مقدار پیش‌فرض را برای همهٔ EventEmitterها تغییر می‌دهد.


5. rawListeners()


این متد آرایه‌ای از Listenerهای خام (شامل Wrapperها مانند once()) را بازمی‌گرداند.


const listeners = emitter.rawListeners('log');
listeners[0].listener(); // اجرای Listener اصلی بدون حذف
listeners[0]();          // اجرای Listener و حذف آن

6. errorMonitor


با استفاده از events.errorMonitor می‌توان خطاها را مانیتور کرد بدون اینکه مصرف شوند. این Listener قبل از Listenerهای معمولی اجرا می‌شود.


7. events.once()


این متد یک Promise ایجاد می‌کند که هنگام انتشار رویداد مشخص resolve می‌شود یا در صورت انتشار خطا reject می‌گردد.


const { once, EventEmitter } = require('node:events');
const ee = new EventEmitter();

(async () => {
  const [value] = await once(ee, 'myevent');
  console.log(value);
})();

8. مدیریت Rejections


با فعال‌سازی گزینهٔ captureRejections، رد شدن Promiseها در Listenerها به‌طور خودکار به رویداد 'error' یا متد Symbol.for('nodejs.rejection') هدایت می‌شود.


نتیجه‌گیری


متدهای پیشرفتهٔ EventEmitter در Node.js ابزارهای قدرتمندی برای مدیریت Listenerها، کنترل خطاها و جلوگیری از Memory Leak فراهم می‌کنند. استفادهٔ صحیح از این متدها باعث افزایش پایداری و قابلیت نگهداری برنامه‌های رویدادمحور می‌شود.


1. events.listenerCount()


این متد تعداد Listenerهای ثبت‌شده برای یک رویداد خاص را بازمی‌گرداند. از نسخهٔ 3.2.0 منسوخ شده و باید از emitter.listenerCount() استفاده شود.


const { EventEmitter, listenerCount } = require('node:events');
const myEmitter = new EventEmitter();
myEmitter.on('event', () => {});
myEmitter.on('event', () => {});
console.log(listenerCount(myEmitter, 'event')); // 2

2. events.on()


این متد یک AsyncIterator ایجاد می‌کند که رویدادهای منتشرشده را به‌صورت غیرهمزمان پردازش می‌کند. هر بار که رویداد منتشر شود، یک آرایه از آرگومان‌ها بازگردانده می‌شود.


const { on, EventEmitter } = require('node:events');
(async () => {
  const ee = new EventEmitter();
  process.nextTick(() => {
    ee.emit('foo', 'bar');
    ee.emit('foo', 42);
  });
  for await (const event of on(ee, 'foo')) {
    console.log(event); // ['bar'], [42]
  }
})();

3. events.setMaxListeners()


این متد محدودیت تعداد Listenerها را برای یک EventEmitter یا EventTarget مشخص می‌کند. مقدار پیش‌فرض 10 است و می‌توان آن را تغییر داد.


const { setMaxListeners, EventEmitter } = require('node:events');
const emitter = new EventEmitter();
setMaxListeners(5, emitter);

4. events.addAbortListener()


این متد Listener ایمن برای رویداد abort در AbortSignal اضافه می‌کند و یک Disposable بازمی‌گرداند تا بتوان آن را حذف کرد.


const { addAbortListener } = require('node:events');
function example(signal) {
  const disposable = addAbortListener(signal, () => {
    console.log('Aborted!');
  });
  disposable[Symbol.dispose]();
}

5. EventEmitterAsyncResource


این کلاس ترکیبی از EventEmitter و AsyncResource است و برای ردیابی منابع غیرهمزمان استفاده می‌شود. رویدادهای منتشرشده در زمینهٔ async مربوط به خود اجرا می‌شوند.


const { EventEmitterAsyncResource } = require('node:events');
const ee = new EventEmitterAsyncResource({ name: 'Q' });
ee.on('foo', () => {
  console.log('running in async context');
});
ee.emit('foo');

6. EventTarget و NodeEventTarget


Node.js نسخهٔ خاصی از EventTarget وب را پیاده‌سازی کرده است. تفاوت‌ها:


  • هیچ سلسله‌مراتب یا propagation وجود ندارد.
  • Listenerها فقط یک‌بار برای هر رویداد ثبت می‌شوند.
  • NodeEventTarget زیرمجموعه‌ای از API EventEmitter را شبیه‌سازی می‌کند.

const target = new EventTarget();
target.addEventListener('foo', (event) => {
  console.log('foo event happened!');
});
target.dispatchEvent(new Event('foo'));

نتیجه‌گیری


ماژول events در Node.js امکانات پیشرفته‌ای برای مدیریت Listenerها، کنترل جریان رویدادها و یکپارچه‌سازی با منابع غیرهمزمان ارائه می‌دهد. استفادهٔ صحیح از این متدها باعث افزایش پایداری، امنیت و قابلیت نگهداری برنامه‌های رویدادمحور می‌شود.


1. events.listenerCount()


تعداد Listenerهای ثبت‌شده برای یک رویداد خاص را بازمی‌گرداند. از نسخهٔ 3.2.0 منسوخ شده و باید از emitter.listenerCount() استفاده شود.


const { EventEmitter, listenerCount } = require('node:events');
const myEmitter = new EventEmitter();
myEmitter.on('event', () => {});
myEmitter.on('event', () => {});
console.log(listenerCount(myEmitter, 'event')); // 2

2. events.on()


یک AsyncIterator ایجاد می‌کند که رویدادهای منتشرشده را به‌صورت غیرهمزمان پردازش می‌کند. هر بار که رویداد منتشر شود، یک آرایه از آرگومان‌ها بازگردانده می‌شود.


const { on, EventEmitter } = require('node:events');
(async () => {
  const ee = new EventEmitter();
  process.nextTick(() => {
    ee.emit('foo', 'bar');
    ee.emit('foo', 42);
  });
  for await (const event of on(ee, 'foo')) {
    console.log(event); // ['bar'], [42]
  }
})();

3. events.setMaxListeners()


محدودیت تعداد Listenerها را برای یک EventEmitter یا EventTarget مشخص می‌کند. مقدار پیش‌فرض 10 است و می‌توان آن را تغییر داد.


const { setMaxListeners, EventEmitter } = require('node:events');
const emitter = new EventEmitter();
setMaxListeners(5, emitter);

4. events.addAbortListener()


یک Listener ایمن برای رویداد abort در AbortSignal اضافه می‌کند و یک Disposable بازمی‌گرداند تا بتوان آن را حذف کرد.


const { addAbortListener } = require('node:events');
function example(signal) {
  const disposable = addAbortListener(signal, () => {
    console.log('Aborted!');
  });
  disposable[Symbol.dispose]();
}

5. EventEmitterAsyncResource


این کلاس ترکیبی از EventEmitter و AsyncResource است و برای ردیابی منابع غیرهمزمان استفاده می‌شود. رویدادهای منتشرشده در زمینهٔ async مربوط به خود اجرا می‌شوند.


const { EventEmitterAsyncResource } = require('node:events');
const ee = new EventEmitterAsyncResource({ name: 'Q' });
ee.on('foo', () => {
  console.log('running in async context');
});
ee.emit('foo');

6. EventTarget و NodeEventTarget


Node.js نسخهٔ خاصی از EventTarget وب را پیاده‌سازی کرده است. تفاوت‌ها:


  • هیچ سلسله‌مراتب یا propagation وجود ندارد.
  • Listenerها فقط یک‌بار برای هر رویداد ثبت می‌شوند.
  • NodeEventTarget زیرمجموعه‌ای از API EventEmitter را شبیه‌سازی می‌کند.

const target = new EventTarget();
target.addEventListener('foo', (event) => {
  console.log('foo event happened!');
});
target.dispatchEvent(new Event('foo'));

نتیجه‌گیری


امکانات پیشرفتهٔ ماژول events در Node.js ابزارهای قدرتمندی برای مدیریت Listenerها، کنترل رویدادهای غیرهمزمان و یکپارچه‌سازی با AbortSignal و منابع async فراهم می‌کنند. شناخت این متدها به توسعه‌دهندگان کمک می‌کند برنامه‌های رویدادمحور پایدارتر و قابل نگهداری‌تر بسازند.


نوشته و پژوهش شده توسط دکتر شاهین صیامی