Node.jsМодуль 1: Введение в Node.js
События и EventEmitter
Паттерн Observer в Node.js
Цель урока
В этом уроке ты научишься:
- Использовать EventEmitter
- Создавать свои события
- Понимать паттерн Observer
EventEmitter
Многие объекты в Node.js являются эмиттерами событий.
Базовое использование
const EventEmitter = require('events');
const emitter = new EventEmitter();
// Подписка на событие
emitter.on('message', (data) => {
console.log('Получено:', data);
});
// Генерация события
emitter.emit('message', 'Привет!');
// Получено: Привет!Несколько аргументов
emitter.on('user', (name, age) => {
console.log(`${name}, ${age} лет`);
});
emitter.emit('user', 'Иван', 25);
// Иван, 25 летМетоды EventEmitter
on / addListener
// Подписка на событие (многократный вызов)
emitter.on('event', callback);
emitter.addListener('event', callback); // то же самоеonce
// Подписка на одноразовое событие
emitter.once('connect', () => {
console.log('Подключено!');
});
emitter.emit('connect'); // Подключено!
emitter.emit('connect'); // ничегоoff / removeListener
function handler(data) {
console.log(data);
}
emitter.on('event', handler);
emitter.off('event', handler);
// или
emitter.removeListener('event', handler);removeAllListeners
// Удалить все обработчики события
emitter.removeAllListeners('event');
// Удалить все обработчики всех событий
emitter.removeAllListeners();listeners
// Получить массив обработчиков
const handlers = emitter.listeners('event');
console.log(handlers.length);listenerCount
// Количество обработчиков
const count = emitter.listenerCount('event');Наследование от EventEmitter
const EventEmitter = require('events');
class Server extends EventEmitter {
constructor() {
super();
this.connections = 0;
}
connect() {
this.connections++;
this.emit('connection', this.connections);
}
disconnect() {
this.connections--;
this.emit('disconnection', this.connections);
}
}
const server = new Server();
server.on('connection', (count) => {
console.log(`Новое подключение. Всего: ${count}`);
});
server.on('disconnection', (count) => {
console.log(`Отключение. Осталось: ${count}`);
});
server.connect(); // Новое подключение. Всего: 1
server.connect(); // Новое подключение. Всего: 2
server.disconnect(); // Отключение. Осталось: 1Обработка ошибок
const emitter = new EventEmitter();
// Обязательно обрабатывай событие 'error'
emitter.on('error', (err) => {
console.error('Ошибка:', err.message);
});
// Без обработчика error — процесс упадёт!
emitter.emit('error', new Error('Что-то пошло не так'));Важно
Если событие error не обработано, Node.js выбросит исключение и завершит процесс.
Асинхронные события
const EventEmitter = require('events');
class DataFetcher extends EventEmitter {
async fetch(url) {
try {
this.emit('start', url);
// Имитация загрузки
await new Promise(resolve => setTimeout(resolve, 1000));
const data = { url, timestamp: Date.now() };
this.emit('data', data);
this.emit('end');
} catch (error) {
this.emit('error', error);
}
}
}
const fetcher = new DataFetcher();
fetcher.on('start', (url) => console.log('Загрузка:', url));
fetcher.on('data', (data) => console.log('Данные:', data));
fetcher.on('end', () => console.log('Завершено'));
fetcher.on('error', (err) => console.error('Ошибка:', err));
fetcher.fetch('https://api.example.com');Практические примеры
Logger
const EventEmitter = require('events');
class Logger extends EventEmitter {
log(level, message) {
const entry = {
level,
message,
timestamp: new Date().toISOString()
};
this.emit('log', entry);
this.emit(level, entry);
}
info(message) { this.log('info', message); }
warn(message) { this.log('warn', message); }
error(message) { this.log('error', message); }
}
const logger = new Logger();
logger.on('log', (entry) => {
console.log(`[${entry.timestamp}] ${entry.level}: ${entry.message}`);
});
logger.on('error', (entry) => {
// Отправить в систему мониторинга
});
logger.info('Приложение запущено');
logger.error('Ошибка подключения');Job Queue
const EventEmitter = require('events');
class JobQueue extends EventEmitter {
constructor() {
super();
this.jobs = [];
}
add(job) {
this.jobs.push(job);
this.emit('added', job);
}
async process() {
while (this.jobs.length > 0) {
const job = this.jobs.shift();
this.emit('processing', job);
try {
await job.execute();
this.emit('completed', job);
} catch (error) {
this.emit('failed', job, error);
}
}
this.emit('empty');
}
}Практика
Задание 1: Таймер с событиями
Задача: Создай класс Timer с событиями tick и done.
Решение:
const EventEmitter = require('events');
class Timer extends EventEmitter {
constructor(seconds) {
super();
this.seconds = seconds;
}
start() {
let remaining = this.seconds;
const interval = setInterval(() => {
this.emit('tick', remaining);
remaining--;
if (remaining < 0) {
clearInterval(interval);
this.emit('done');
}
}, 1000);
}
}
const timer = new Timer(5);
timer.on('tick', (s) => console.log(`Осталось: ${s}`));
timer.on('done', () => console.log('Время вышло!'));
timer.start();Задание 2: Чат
Задача: Создай простой чат с событиями.
Решение:
const EventEmitter = require('events');
class Chat extends EventEmitter {
constructor() {
super();
this.users = [];
}
join(user) {
this.users.push(user);
this.emit('join', user);
}
leave(user) {
this.users = this.users.filter(u => u !== user);
this.emit('leave', user);
}
send(user, message) {
this.emit('message', { user, message, time: new Date() });
}
}
const chat = new Chat();
chat.on('join', (user) => console.log(`${user} присоединился`));
chat.on('leave', (user) => console.log(`${user} вышел`));
chat.on('message', (msg) => console.log(`[${msg.user}]: ${msg.message}`));
chat.join('Иван');
chat.send('Иван', 'Привет всем!');
chat.leave('Иван');Проверь себя
- Что делает метод
once? - Почему важно обрабатывать событие
error? - Как получить количество обработчиков события?