События и EventEmitter
Событие — сообщение «что-то произошло», на которое могут откликнуться другие части программы. В браузере вы подписывались на клики через addEventListener. В Node.js ту же роль играет класс EventEmitter из модуля node:events. На нём построены сервер, потоки и многие другие встроенные объекты.
Подписка и отправка
Метод on() подписывает функцию на событие, emit() отправляет событие и передаёт данные всем подписчикам:
import { EventEmitter } from 'node:events';
const blog = new EventEmitter();
blog.on('post', (title) => {
console.log(`Новая статья: ${title}`);
});
blog.emit('post', 'Привет, Node.js');
blog.emit('post', 'Работа с файлами');Выведутся две строки «Новая статья: ...». Имя события — любая строка, которую вы придумаете.
Несколько подписчиков
На одно событие можно подписать сколько угодно функций — они вызываются по порядку подписки. Так разные части программы реагируют на одно и то же, ничего не зная друг о друге:
import { EventEmitter } from 'node:events';
const blog = new EventEmitter();
blog.on('comment', (comment) => console.log(`Записываем в журнал: ${comment.author}`));
blog.on('comment', (comment) => console.log(`Письмо автору статьи ${comment.postId}`));
blog.emit('comment', { postId: 1, author: 'Олег', text: 'Спасибо!' });Свой класс с событиями
Обычно от EventEmitter наследуют свой класс. Создайте файл src/blog-events.js:
// src/blog-events.js
import { EventEmitter } from 'node:events';
export class Blog extends EventEmitter {
#posts = [];
addPost(title) {
const post = { id: this.#posts.length + 1, title };
this.#posts.push(post);
this.emit('post:created', post);
return post;
}
}Используйте его в app.js:
// app.js
import { Blog } from './src/blog-events.js';
const blog = new Blog();
blog.on('post:created', (post) => console.log(`Опубликовано: №${post.id} ${post.title}`));
blog.addPost('Привет, Node.js');node app.js выведет «Опубликовано: №1 Привет, Node.js». Класс Blog не знает, кто его слушает, — он только сообщает о событии.
Одноразовые обработчики и отписка
| Метод | Что делает |
|---|---|
on(name, fn) | Подписывает функцию |
once(name, fn) | Подписывает на одно срабатывание |
off(name, fn) | Отписывает функцию |
emit(name, ...data) | Отправляет событие |
listenerCount(name) | Число подписчиков |
import { EventEmitter } from 'node:events';
const server = new EventEmitter();
server.once('ready', () => console.log('Сервер готов'));
server.emit('ready');
server.emit('ready');
console.log(server.listenerCount('ready')); // 0«Сервер готов» выведется один раз: после первого срабатывания обработчик удаляется сам.
Частая ошибка
Событие error особенное. Если его отправить, а подписчика нет, EventEmitter бросит ошибку и программа упадёт. Поэтому у объектов, которые могут сообщать об ошибках, всегда подписывайтесь на error:
import { EventEmitter } from 'node:events';
const blog = new EventEmitter();
blog.on('error', (error) => console.log(`Ошибка: ${error.message}`));
blog.emit('error', new Error('не удалось сохранить статью'));Без строки с blog.on('error', ...) этот код завершился бы с необработанной ошибкой.