EventEmitter — on, emit, once, off, custom event
Muammodan boshlaylik
Siz buyurtma tizimi yozayapsiz. Buyurtma yaratilganda bir nechta narsa bo'lishi kerak:
- Email yuborilsin
- SMS yuborilsin
- Ombor yangilansin
- 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