EventEmitter — on, emit, once, off, custom event

~20 daq · Node.js · Core Modullar

Muammodan boshlaylik

Siz buyurtma tizimi yozayapsiz. Buyurtma yaratilganda bir nechta narsa bo'lishi kerak:

  1. Email yuborilsin
  2. SMS yuborilsin
  3. Ombor yangilansin
  4. Admin panelga bildirishnoma ketsin

Buni qanday yozasiz?

async function createOrder(data) {
  const order = await db.save(data);

  // Hammasi shu yerda — bog'liq, chalkash:
  await sendEmail(order);
  await sendSMS(order);
  await updateInventory(order);
  await notifyAdmin(order);

  return order;
}

Bu ishlaydi, lekin muammo bor. createOrder funksiyasi email, SMS, ombor, admin — bularning hammasini bilishi kerak. Yangi narsa qo'shilsa — createOrder ni o'zgartirish kerak. Funksiyalar bir-biriga bog'liq (coupled).

EventEmitter bu muammoni hal qiladi.


EventEmitter nima?

EventEmitter — "voqea bo'ldi, kim qiziqsa — shu yerda" degan mexanizm. Ikki tomon bor:

  • Emit qiluvchi — "buyurtma yaratildi" deb e'lon qiladi
  • Tinglovchilar — "buyurtma yaratildi" eventini eshitib, o'z ishini qiladi
createOrder  →  emit('order:created', order)
                        │
              ┌─────────┼─────────┐
              ↓         ↓         ↓
          sendEmail  sendSMS  updateInventory

createOrder endi email, SMS haqida bilmaydi. U faqat "buyurtma yaratildi" deb e'lon qiladi. Kim nima qilishini — tinglovchilar hal qiladi. Yangi tinglovchi qo'shish uchun createOrder ni o'zgartirish shart emas.

Bu pattern — Observer yoki Pub/Sub deb ataladi. Node.js ning ko'p qismi shu asosda qurilgan: fs, http, stream — barchasi EventEmitter dan meros olgan.


EventEmitter — asosiy ishlatilish

const EventEmitter = require('events');

const emitter = new EventEmitter();

// Tinglovchi qo'shish:
emitter.on('salom', (name) => {
  console.log(`Salom, ${name}!`);
});

// Event chiqarish:
emitter.emit('salom', 'Ali');   // 'Salom, Ali!'
emitter.emit('salom', 'Vali');  // 'Salom, Vali!'

on — tinglovchi qo'shadi. Event har chiqarilganda chaqiriladi. emit — eventni chiqaradi, barcha tinglovchilarga xabar beradi.


Bir eventga bir nechta tinglovchi

emitter.on('data', (chunk) => {
  console.log('Tinglovchi 1:', chunk.length, 'bayt');
});

emitter.on('data', (chunk) => {
  console.log('Tinglovchi 2: ma\'lumot saqlandi');
});

emitter.emit('data', Buffer.from('Salom'));
// Tinglovchi 1: 5 bayt
// Tinglovchi 2: ma'lumot saqlandi

Ikkalasi ham chaqiriladi — qo'shilgan tartibda.


once — faqat bir marta

once — tinglovchi faqat bir marta chaqiriladi, keyin avtomatik o'chiriladi.

emitter.once('connect', () => {
  console.log('Ulandi!');
});

emitter.emit('connect'); // 'Ulandi!'
emitter.emit('connect'); // hech narsa — tinglovchi o'chirilgan

Qachon kerak? Bir martalik hodisalar uchun: server ishga tushdi, ma'lumotlar bazasiga ulandi, fayl birinchi marta o'qildi.


off — tinglovchini o'chirish

off (yoki removeListener) — qo'shilgan tinglovchini o'chiradi.

function onData(chunk) {
  console.log('Ma\'lumot:', chunk);
}

emitter.on('data', onData);

emitter.emit('data', 'birinchi');  // 'Ma'lumot: birinchi'

emitter.off('data', onData);

emitter.emit('data', 'ikkinchi');  // hech narsa — tinglovchi o'chirildi

Muhim: off uchun funksiya reference kerak. Shuning uchun tinglovchi alohida o'zgaruvchiga saqlanadi. Anonymous funksiya bilan off ishlamaydi:

// Xato — o'chirib bo'lmaydi:
emitter.on('data', (chunk) => console.log(chunk));
emitter.off('data', (chunk) => console.log(chunk)); // boshqa funksiya!

// To'g'ri:
const handler = (chunk) => console.log(chunk);
emitter.on('data', handler);
emitter.off('data', handler); // bir xil reference

Custom EventEmitter — meros olish

Amalda ko'pincha EventEmitter dan meros olinadi — o'z klassiga event qo'shish uchun.

Buyurtma tizimi misoli:

const EventEmitter = require('events');

class OrderService extends EventEmitter {
  async create(data) {
    // Buyurtmani saqlash
    const order = { id: Date.now(), ...data, status: 'created' };

    // Event chiqarish — kim nima qilishini bilmaymiz
    this.emit('order:created', order);

    return order;
  }

  async cancel(orderId) {
    const order = { id: orderId, status: 'cancelled' };
    this.emit('order:cancelled', order);
    return order;
  }
}

const orderService = new OrderService();

// Tinglovchilar — alohida, mustaqil:
orderService.on('order:created', async (order) => {
  await sendEmail(order.email, 'Buyurtmangiz qabul qilindi');
});

orderService.on('order:created', async (order) => {
  await updateInventory(order.items);
});

orderService.on('order:cancelled', async (order) => {
  await sendEmail(order.email, 'Buyurtmangiz bekor qilindi');
});

// Ishlatish:
const order = await orderService.create({ email: 'ali@example.com', items: [...] });

Endi OrderService email, SMS, ombor haqida hech narsa bilmaydi. Yangi tinglovchi qo'shish uchun faqat on chaqiriladi — OrderService o'zgarmaydi.


error eventi — maxsus holat

error eventi — EventEmitter da maxsus. Agar error eventi chiqarilsa va tinglovchi bo'lmasa — Node.js crash bo'ladi.

const emitter = new EventEmitter();

emitter.emit('error', new Error('Nimadir noto\'g\'ri'));
// Tinglovchi yo'q → Node.js crash: UnhandledError

Shuning uchun error eventiga har doim tinglovchi qo'shiladi:

emitter.on('error', (err) => {
  console.error('Xato:', err.message);
  // Crash bo'lmaydi — xato ushlanadi
});

eventNames va listenerCount

Debugging uchun foydali:

emitter.on('data', () => {});
emitter.on('data', () => {});
emitter.on('end',  () => {});

console.log(emitter.eventNames());
// ['data', 'end']

console.log(emitter.listenerCount('data'));
// 2

MaxListeners ogohlantirishi

Standart bo'yicha bir eventga 10 tadan ko'p tinglovchi qo'shilsa — Node.js ogohlantirish chiqaradi:

MaxListenersExceededWarning: Possible EventEmitter memory leak detected.
11 data listeners added.

Bu memory leak belgisi bo'lishi mumkin — tinglovchilar qo'shilayapti, lekin o'chirilmayapti. Agar haqiqatan ko'p tinglovchi kerak bo'lsa:

emitter.setMaxListeners(20); // limitni oshirish
emitter.setMaxListeners(0);  // cheksiz (ehtiyot bo'ling)

Tez-tez uchraydigan xatolar

error eventini unutish:

const stream = fs.createReadStream('yoq.txt');
stream.on('data', () => {});
// error eventi yo'q → fayl topilmasa Node.js crash bo'ladi

Tinglovchini o'chirmaslik (memory leak):

// Har so'rovda yangi tinglovchi qo'shilsa:
app.get('/data', (req, res) => {
  emitter.on('update', (data) => res.json(data)); // har safar qo'shiladi!
});
// Minglab so'rovdan keyin — minglab tinglovchi, xotira to'lib ketadi

Tinglovchi ishlatib bo'lingach off bilan o'chiriladi yoki once ishlatiladi.

Asinxron tinglovchidagi xatolarni ushlamaslik:

emitter.on('data', async (data) => {
  await processData(data); // xato chiqsa — ushlanmaydi!
});

// To'g'ri:
emitter.on('data', async (data) => {
  try {
    await processData(data);
  } catch (err) {
    emitter.emit('error', err); // error eventiga uzatish
  }
});

Xulosa

EventEmitter — Node.js da voqeaga asoslangan dasturlashning asosi. on tinglovchi qo'shadi, emit eventni chiqaradi, once bir martalik tinglovchi, off o'chiradi.

Asosiy foyda — decoupling. Voqea chiqaruvchi va tinglovchilar bir-birini bilmaydi. Yangi funksionallik qo'shish uchun mavjud kodni o'zgartirish shart emas — faqat yangi tinglovchi qo'shiladi.

fs, http, stream — Node.js ning ko'p qismi EventEmitter asosida qurilgan. Shuning uchun ularni tushunish uchun EventEmitter ni bilish muhim.


Keyingi dars

17-dars: Stream nima — Readable, Writable, Duplex, Transform