Пошук уроків, статей та іншого контенту
Створюйте власні події й використовуйте EventEmitter для слабкого зв’язування компонентів.
Подія — це повідомлення про те, що в програмі щось сталося:
користувача створено;
замовлення оформлено;
файл завантажено;
платіж успішно виконано.
Компонент, у якому сталася подія, називають видавцем. Компонент, який реагує на подію, називають слухачем.
Наприклад, після створення замовлення можуть виконуватися різні дії:
надсилання електронного листа;
запис інформації в журнал;
оновлення статистики.
Без подій код створення замовлення мусив би безпосередньо викликати всі ці компоненти. У результаті компоненти були б сильно пов’язані між собою.
EventEmitter дає змогу створювати події та слухачів для них. Видавець не повинен знати, хто саме слухає подію.
У Node.js клас EventEmitter доступний у вбудованому модулі events.
const { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
emitter.on('message', (text) => {
console.log(`Отримано повідомлення: ${text}`);
});
emitter.emit('message', 'Привіт, Node.js!');У цьому прикладі:
EventEmitter — клас для роботи з подіями;
emitter — екземпляр цього класу;
on() — додає слухача;
'message' — назва події;
emit() — створює подію;
'Привіт, Node.js!' — дані, передані слухачу.
Результат виконання:
Отримано повідомлення: Привіт, Node.js!Назва події є рядком. Важливо, щоб назва в on() і emit() збігалася.
Метод on() додає функцію, яка виконується щоразу, коли відбувається подія.
const { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
emitter.on('userCreated', (user) => {
console.log(`Користувача ${user.name} створено`);
});
emitter.on('userCreated', (user) => {
console.log(`Надсилаємо привітання на ${user.email}`);
});
emitter.emit('userCreated', {
name: 'Олена',
email: 'olena@example.com'
});Одна подія може мати кілька слухачів. Усі вони будуть викликані в порядку додавання.
Метод emit() може передавати слухачам будь-які значення:
const { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
emitter.on('orderCreated', (orderId, total) => {
console.log(`Замовлення №${orderId} на суму ${total} грн створено`);
});
emitter.emit('orderCreated', 101, 799);Зазвичай зручно передавати один об’єкт. Так код легше розширювати:
emitter.emit('orderCreated', {
id: 101,
total: 799,
customerEmail: 'customer@example.com'
});У такому разі слухач отримує один параметр — об’єкт події.
Метод once() додає слухача, який спрацює лише один раз. Після першого виклику події слухач буде автоматично видалений.
const { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
emitter.once('applicationStarted', () => {
console.log('Застосунок запущено вперше');
});
emitter.emit('applicationStarted');
emitter.emit('applicationStarted');Результат:
Застосунок запущено впершеДругий виклик emit() уже не викликає функцію.
once() корисний для подій на кшталт:
першого запуску застосунку;
одноразової ініціалізації;
завершення певної операції.
Щоб видалити слухача, потрібно передати в off() ту саму функцію, яку раніше передали в on().
const { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
function handleMessage(message) {
console.log(message);
}
emitter.on('message', handleMessage);
emitter.emit('message', 'Перше повідомлення');
emitter.off('message', handleMessage);
emitter.emit('message', 'Друге повідомлення');Результат:
Перше повідомленняПісля виклику off() функція більше не реагує на подію.
Не вийде видалити слухача, створивши іншу функцію з таким самим кодом:
emitter.on('message', () => {
console.log('Повідомлення');
});
// Це інша функція, тому попередній слухач не буде видалений
emitter.off('message', () => {
console.log('Повідомлення');
});Функції порівнюються як окремі об’єкти, тому для видалення потрібно зберегти посилання на початкову функцію.
Доменна подія не повинна описувати технічну деталь на кшталт databaseQueryFinished. Вона має описувати важливий факт із погляду бізнес-логіки:
userRegistered;
orderCreated;
paymentCompleted;
productAddedToCart.
Такі назви допомагають зрозуміти, що відбулося в системі.
Створимо сервіс замовлень. Він створюватиме замовлення та повідомлятиме про це через подію. Окремі слухачі реагуватимуть на подію незалежно один від одного.
const { EventEmitter } = require('node:events');
class OrderService extends EventEmitter {
constructor() {
super();
this.orders = [];
}
createOrder(customerEmail, total) {
const order = {
id: this.orders.length + 1,
customerEmail,
total
};
this.orders.push(order);
this.emit('orderCreated', order);
return order;
}
}
const orderService = new OrderService();
orderService.on('orderCreated', (order) => {
console.log(
`Надсилаємо підтвердження на ${order.customerEmail}`
);
});
orderService.on('orderCreated', (order) => {
console.log(
`Записуємо замовлення №${order.id} у журнал`
);
});
const order = orderService.createOrder('olena@example.com', 1250);
console.log('Створено замовлення:', order);Можливий результат:
Надсилаємо підтвердження на olena@example.com
Записуємо замовлення №1 у журнал
Створено замовлення: {
id: 1,
customerEmail: 'olena@example.com',
total: 1250
}OrderService знає лише про подію orderCreated. Він не знає:
хто надсилає повідомлення;
хто записує дані в журнал;
скільки слухачів існує.
Тому можна додати або видалити реакцію на подію, не змінюючи метод createOrder().
Слухачі події викликаються в порядку їх додавання. Виклик emit() за замовчуванням є синхронним: спочатку виконаються слухачі, а потім програма перейде до наступного рядка.
const { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
emitter.on('done', () => {
console.log('Слухач виконався');
});
console.log('До події');
emitter.emit('done');
console.log('Після події');Результат:
До події
Слухач виконався
Після подіїТому emit() не перетворює обробник на фонову операцію і не робить його автоматично асинхронним.
EventEmitter має спеціальну подію 'error'. Якщо подію помилки викликати без слухача, Node.js може завершити роботу процесу.
const { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
emitter.on('error', (error) => {
console.error('Сталася помилка:', error.message);
});
emitter.emit('error', new Error('Не вдалося обробити замовлення'));Слухач 'error' потрібно додавати, якщо ваш об’єкт може повідомляти про помилки через події.
EventEmitter добре підходить, коли:
одна дія має кілька незалежних реакцій;
видавець не повинен знати про слухачів;
подія відбувається всередині одного Node.js-процесу;
потрібно повідомити кілька частин програми про зміну стану.
Для невеликої програми достатньо одного екземпляра EventEmitter або класу, що його наслідує.
Назви подій варто робити:
конкретними;
зрозумілими;
такими, що описують уже виконану дію.
Наприклад, orderCreated означає «замовлення створено», а не «створити замовлення».
emit() до додавання слухачаconst { EventEmitter } = require('node:events');
const emitter = new EventEmitter();
emitter.emit('ready');
emitter.on('ready', () => {
console.log('Готово');
});У цьому прикладі слухач нічого не виведе, бо подія вже відбулася. EventEmitter не зберігає події для майбутніх слухачів.
Якщо подія може відбутися до підключення слухача, стан або результат операції потрібно зберігати окремо.
emitter.on('orderCreated', () => {
console.log('Замовлення створено');
});
emitter.emit('order-created');'orderCreated' і 'order-created' — різні назви, тому слухач не спрацює.
Якщо щоразу додавати новий слухач під час виклику функції, одна подія може спричинити багаторазове виконання обробника.
function startLogging(emitter) {
emitter.on('message', () => {
console.log('Повідомлення отримано');
});
}Повторний виклик startLogging(emitter) додасть ще одного слухача. Підключайте слухачів у передбаченому місці та видаляйте їх, коли вони більше не потрібні.
Подія повинна містити дані, необхідні слухачу. Якщо слухачу потрібен ідентифікатор замовлення та адреса клієнта, передайте їх у події, а не змушуйте слухача самостійно шукати ці дані в іншому місці.
EventEmitter використовується для створення та обробки подій у Node.js.
on() додає слухача, який виконується під час кожного виклику події.
once() додає одноразового слухача.
emit() створює подію та передає їй дані.
off() видаляє раніше доданого слухача.
Доменні події описують важливі факти предметної області, наприклад orderCreated.
Події зменшують залежність між компонентами: видавець не повинен знати про слухачів.
Обробник події 'error' потрібно додавати, якщо програма повідомляє про помилки через EventEmitter.