Вступление
В этом руководстве мы рассмотрим собственный класс EventEmitter
Вы
узнаете о событиях, о том, что вы можете делать с EvenEmitter
, и о
том, как использовать события в своем приложении.
Мы также рассмотрим, какие другие собственные модули расширяют
EventEmitter
и рассмотрим несколько примеров, чтобы понять, что
происходит за кулисами.
Итак, вкратце, мы рассмотрим почти все, что вам нужно знать о классе
EventEmitter
В этом руководстве мы будем использовать некоторые базовые функции ES6, такие как классы JavaScript и стрелочные функции. Это полезно, но не обязательно, если вы уже знакомы с синтаксисом ES6.
Что такое событие?
Вся парадигма программного обеспечения вращается вокруг событий и их использования. Архитектура, управляемая событиями, довольно распространена в настоящее время, и приложения, управляемые событиями, создают, обнаруживают и реагируют на различные виды событий.
Можно утверждать, что ядро Node.js частично управляется событиями,
поскольку многие собственные модули, такие как файловая система ( fs
)
и stream
модуль, сами написаны как EventEmitter
.
В программировании, управляемом событиями, событие является результатом одного или нескольких действий. Это может быть, например, действие пользователя или периодический выходной сигнал датчика.
Вы можете рассматривать программы, управляемые событиями, как модели публикации-подписки, в которых издатель инициирует события, а подписчики их слушают и действуют соответствующим образом.
Например, предположим, что у нас есть сервер изображений, на который
пользователи могут загружать изображения. В программировании,
управляемом событиями, такое действие, как загрузка изображения,
вызывает событие. Чтобы воспользоваться этим, на это событие 1..n
После запуска события загрузки подписчик может отреагировать на него, отправив электронное письмо администратору веб-сайта, сообщив ему, что пользователь загрузил фотографию. Другой подписчик может собирать информацию о действии и сохранять ее в базе данных.
Эти события обычно независимы друг от друга, хотя они также могут быть зависимыми.
Что такое EventEmitter?
Класс EventEmitter
- это встроенный класс, который находится в модуле
events
По документации:
Большая часть API ядра Node.js построена на идиоматической асинхронной управляемой событиями архитектуре, в которой определенные виды объектов (называемые «эмиттерами») генерируют именованные события, которые вызывают
Function
объектов («слушателей») »
Этот класс можно до некоторой степени описать как вспомогательную реализацию модели pub / sub, поскольку он помогает источникам событий (издателям) публиковать события (сообщения), а слушателям (подписчикам) действовать в отношении этих событий - простым способом.
Создание EventEmitters
При этом давайте продолжим и создадим EventEmitter
. Это можно сделать
либо путем создания экземпляра самого класса, либо путем его реализации
через настраиваемый класс и последующего создания экземпляра этого
класса.
Создание объекта EventEmitter
Начнем с простого объекта, излучающего события. Мы создадим
EventEmitter
который каждую секунду будет генерировать событие,
содержащее информацию о времени безотказной работы приложения.
Сначала импортируйте класс EventEmitter
из модулей events
const { EventEmitter } = require('events');
Затем создадим EventEmitter
:
const timerEventEmitter = new EventEmitter();
Опубликовать событие из этого объекта очень просто:
timerEventEmitter.emit("update");
Мы указали название события и опубликовали его как событие. Однако ничего не происходит, поскольку нет слушателя, который бы реагировал на это событие. Сделаем так, чтобы это событие повторялось каждую секунду.
Используя метод setInterval()
, создается таймер, который будет
публиковать update
каждую секунду:
let currentTime = 0;
// This will trigger the update event each passing second
setInterval(() => {
currentTime++;
timerEventEmitter.emit('update', currentTime);
}, 1000);
EventEmitter
принимает имя события и произвольный набор аргументов. В
этом случае мы передали eventName
как update
и currentTime
как
время от начала приложения.
Мы запускаем эмиттер с помощью emit()
, который отправляет событие с
предоставленной нами информацией.
Когда наш эмиттер событий готов, давайте подпишем на него слушателя событий:
timerEventEmitter.on('update', (time) => {
console.log('Message Received from publisher');
console.log(`${time} seconds passed since the program started`);
});
Использование on()
передачей имени события, чтобы указать, к какому из
них мы хотели бы прикрепить слушателя, позволяет нам создавать
слушателей. При update
запускается метод, который регистрирует
время. Вы можете добавлять одного и того же слушателя снова и снова, и
каждый из них будет подписываться на событие.
Второй аргумент функции on()
- это обратный вызов, который может
принимать любое количество дополнительных данных, которые были
отправлены событием. Каждый слушатель может выбрать, какие данные ему
нужны, после сохранения порядка.
Запуск этого скрипта должен дать:
Message Received from publisher
1 seconds passed since the program started
Message Received from publisher
2 seconds passed since the program started
Message Received from publisher
3 seconds passed since the program started
...
Напротив, мы можем использовать метод once()
для подписки - если вам
нужно выполнить что-то только при первом срабатывании события:
timerEventEmitter.once('update', (time) => {
console.log('Message Received from publisher');
console.log(`${time} seconds passed since the program started`);
});
Запуск этого кода даст:
Message Received from publisher
1 seconds passed since the program started
EventEmitter с несколькими слушателями
Теперь давайте создадим другой тип генератора событий с тремя слушателями. Это будет обратный отсчет. Один слушатель будет обновлять пользователя каждую секунду, один слушатель будет уведомлять пользователя, когда обратный отсчет приближается к концу, и последний слушатель сработает, когда обратный отсчет закончится:
update
- это событие будет срабатывать каждую секундуend
- это событие сработает по окончании обратного отсчета.end-soon
- это событие сработает за 2 секунды до окончания обратного отсчета.
Создадим функцию, которая создает этот эмиттер событий и возвращает его:
const countDown = (countdownTime) => {
const eventEmitter = new EventEmitter();
let currentTime = 0;
// This will trigger the update event each passing second
const timer = setInterval(() => {
currentTime++;
eventEmitter.emit('update', currentTime);
// Check if countdown has reached to the end
if (currentTime === countdownTime) {
clearInterval(timer);
eventEmitter.emit('end');
}
// Check if countdown will end in 2 seconds
if (currentTime === countdownTime - 2) {
eventEmitter.emit('end-soon');
}
}, 1000);
return eventEmitter;
};
В этой функции мы запустили событие на основе интервала, update
При первом if
мы проверяем, достиг ли обратный отсчет конца, и
останавливаем событие на основе интервала. Если это так, мы запускаем
end
событие.
Во втором условии мы проверяем, находится ли обратный отсчет через 2
секунды до окончания, и публикуем end-soon
если это так.
Теперь давайте добавим несколько подписчиков к этому эмиттеру событий:
const myCountDown = countDown(5);
myCountDown.on('update', (t) => {
console.log(`${t} seconds since the timer started`);
});
myCountDown.on('end', () => {
console.log('Countdown is completed');
});
myCountDown.on('end-soon', () => {
console.log('Count down will end in 2 seconds');
});
Этот код должен дать:
1 seconds has been passed since the timer started
2 seconds has been passed since the timer started
3 seconds has been passed since the timer started
Count down will end in 2 seconds
4 seconds has been passed since the timer started
5 seconds has been passed since the timer started
Countdown is completed
Расширение EventEmitter
В этом разделе давайте создадим эмиттер событий с той же
функциональностью, расширив класс EventEmitter
Сначала создайте
CountDown
который будет обрабатывать события:
const { EventEmitter } = require('events');
class CountDown extends EventEmitter {
constructor(countdownTime) {
super();
this.countdownTime = countdownTime;
this.currentTime = 0;
}
startTimer() {
const timer = setInterval(() => {
this.currentTime++;
this.emit('update', this.currentTime);
// Check if countdown has reached to the end
if (this.currentTime === this.countdownTime) {
clearInterval(timer);
this.emit('end');
}
// Check if countdown will end in 2 seconds
if (this.currentTime === this.countdownTime - 2) {
this.emit('end-soon');
}
}, 1000);
}
}
Как видите, мы можем использовать this.emit()
напрямую внутри класса.
Кроме того, startTimer()
используется, чтобы мы могли контролировать,
когда начинается обратный отсчет. В противном случае он начнется, как
только объект будет создан.
Создадим новый объект CountDown
и подпишемся на него:
const myCountDown = new CountDown(5);
myCountDown.on('update', (t) => {
console.log(`${t} seconds has been passed since the timer started`);
});
myCountDown.on('end', () => {
console.log('Countdown is completed');
});
myCountDown.on('end-soon', () => {
console.log('Count down will be end in 2 seconds');
});
myCountDown.startTimer();
Выполнение этого приведет к:
1 seconds has been passed since the timer started
2 seconds has been passed since the timer started
3 seconds has been passed since the timer started
Count down will be end in 2 seconds
4 seconds has been passed since the timer started
5 seconds has been passed since the timer started
Countdown is completed
Псевдоним для функции on()
- addListener()
. Рассмотрим
прослушиватель событий " end-soon
myCountDown.on('end-soon', () => {
console.log('Count down will be end in 2 seconds');
});
Мы могли бы сделать то же самое с addListener()
вот так:
myCountDown.addListener('end-soon', () => {
console.log('Count down will be end in 2 seconds');
});
Они оба работают. Они почти как синонимы. Однако большинство
программистов предпочитают использовать on()
.
Важные функции EventEmitter
Давайте посмотрим на некоторые важные функции, которые мы можем
использовать в EventEmitter
s.
eventNames ()
Эта функция вернет все имена активных слушателей в виде массива:
const myCountDown = new CountDown(5);
myCountDown.on('update', (t) => {
console.log(`${t} seconds has been passed since the timer started`);
});
myCountDown.on('end', () => {
console.log('Countdown is completed');
});
myCountDown.on('end-soon', () => {
console.log('Count down will be end in 2 seconds');
});
console.log(myCountDown.eventNames());
Выполнение этого кода приведет к:
[ 'update', 'end', 'end-soon' ]
Если бы мы подписались на другое событие, такое как
myCount.on('some-event', ...)
, новое событие также будет добавлено в
массив.
Имейте в виду, что этот метод не возвращает опубликованные события. Он возвращает список событий, на которые подписаны.
removeListener ()
Как следует из названия, эта функция удаляет подписанный обработчик из
EventEmitter
:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
const f1 = () => {
console.log('f1 Triggered');
}
const f2 = () => {
console.log('f2 Triggered');
}
emitter.on('some-event', f1);
emitter.on('some-event', f2);
emitter.emit('some-event');
emitter.removeListener('some-event', f1);
emitter.emit('some-event');
После срабатывания первого события, поскольку f1
и f2
- обе функции
будут выполнены. После этого мы удалили f1
из EventEmitter
. Когда
мы снова испустим событие, выполнится f2
f1 Triggered
f2 Triggered
f2 Triggered
Псевдоним для removeListener()
- off()
. Например, мы могли бы
написать:
emitter.removeListener('some-event', f1);
В виде:
emitter.off('some-event', f1);
Оба они имеют одинаковый эффект.
removeAllListeners ()
Опять же, как следует из названия, эта функция удалит всех слушателей из
всех событий EventEmitter
:
const { EventEmitter } = require('events');
const emitter = new EventEmitter();
const f1 = () => {
console.log('f1 Triggered');
}
const f2 = () => {
console.log('f2 Triggered');
}
emitter.on('some-event', f1);
emitter.on('some-event', f2);
emitter.emit('some-event');
emitter.removeAllListeners();
emitter.emit('some-event');
Первый emit()
запустит и f1
и f2
поскольку они активны в это
время. После их удаления emit()
выдаст событие, но слушатели не
ответят на него:
f1 Triggered
f2 Triggered
Обработка ошибок
Если вы хотите выдать ошибку с помощью EventEmitter
, это должно быть
сделано с помощью имени события error
Это стандартно для всех
EventEmitter
в Node.js. Это событие должно также сопровождаться
Error
объекта. Например, событие ошибки может генерироваться следующим
образом:
myEventEmitter.emit('error', new Error('Something bad happened'));
Любые прослушиватели error
должны иметь обратный вызов с одним
аргументом для захвата Error
и его корректной обработки. Если
EventEmitter
генерирует error
, но нет подписчиков на error
,
программа Node.js выдаст Error
которая была выдана.
Это в конечном итоге остановит процесс Node.js от запуска и выхода из вашей программы, одновременно отображая трассировку стека для ошибки в консоли.
Предположим, что в нашем классе CountDown
countdownTime
не может
начинаться со значения меньше 2, потому что в противном случае
end-soon
В таком случае выдадим сообщение error
:
class CountDown extends EventEmitter {
constructor(countdownTime) {
super();
if (countdownTimer < 2) {
this.emit('error', new Error('Value of the countdownTimer cannot be less than 2'));
}
this.countdownTime = countdownTime;
this.currentTime = 0;
}
// ...........
}
Обработка этой ошибки обрабатывается так же, как и другие события:
myCountDown.on('error', (err) => {
console.error('There was an error:', err);
});
Считается хорошей практикой всегда иметь прослушиватель событий error
Собственные модули, использующие EventEmitter
Многие собственные модули в Node.js расширяют EventEmitter
и, таким
образом, сами являются источниками событий.
Отличный пример - класс Stream
.
Официальная документация гласит:
Потоки могут быть доступны для чтения, записи или и того, и другого. Все потоки являются экземплярами
EventEmitter
.
Давайте посмотрим на классическое использование Stream
const fs = require('fs');
const writer = fs.createWriteStream('example.txt');
for (let i = 0; i < 100; i++) {
writer.write(`hello, #${i}!\n`);
}
writer.on('finish', () => {
console.log('All writes are now complete.');
});
writer.end('This is the end\n');
Однако между операцией записи и writer.end()
мы добавили слушателя. По
завершении Stream
генерирует finished
Другие события, такие как
error
, pipe
и unpipe
, когда возникает ошибка или поток чтения
передается по конвейеру или не передается из потока записи.
Еще один примечательный класс - это child_process
и его метод
spawn()
const { spawn } = require('child_process');
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`child process exited with code ${code}`);
});
Когда child_process
записывает в стандартную выходной трубу, data
событие stdout
(который также extends EventEmitter
) будет
срабатывать. Когда выходной поток обнаруживает ошибку, data
отправляется из канала stderr
.
Наконец, после выхода из процесса запускается событие close
Заключение
Архитектура, управляемая событиями, позволяет нам создавать системы,
которые не связаны друг с другом, но очень взаимосвязаны . События
представляют собой результат определенного действия, и 1..n
слушателей, которые будут прослушивать их и реагировать на них.
В этой статье мы EventEmitter
класс EventEmitter и его функции. Мы
создали его и использовали напрямую, а также расширили его поведение до
настраиваемого объекта.
Наконец, мы рассмотрели некоторые примечательные функции класса.
Как всегда, исходный код доступен на GitHub .