Статьи

Использование Node’s Event Module

Когда я впервые услышал о Node.js , я подумал, что это просто реализация JavaScript для сервера. Но на самом деле это гораздо больше: он поставляется с множеством встроенных функций, которые вы не получаете в браузере. Одной из таких функциональных возможностей является модуль Event, который имеет класс EventEmitter . Мы рассмотрим это в этом уроке.


Итак, что именно делает класс EventEmitter ? Проще говоря, он позволяет вам прослушивать «события» и назначать действия, выполняемые при возникновении этих событий. Если вы знакомы с интерфейсным JavaScript, вы будете знать о событиях мыши и клавиатуры, которые происходят при определенных взаимодействиях с пользователем. Они очень похожи, за исключением того, что мы можем генерировать события самостоятельно, когда мы этого хотим, и не нужны на основе взаимодействия с пользователем. Принципы, на которых основан EventEmitter , называются моделью публикации / подписки, потому что мы можем подписаться на события и затем публиковать их. Существует много интерфейсных библиотек, созданных с поддержкой pub / sub, но в Node это встроено.

Другой важный вопрос: зачем вам использовать модель событий? В Node это альтернатива глубоко вложенным обратным вызовам. Многие методы Node запускаются асинхронно, что означает, что для запуска кода после завершения метода необходимо передать метод обратного вызова в функцию. Со временем ваш код будет выглядеть как гигантская воронка. Чтобы предотвратить это, многие классы узлов генерируют события, которые вы можете прослушивать. Это позволяет вам организовать свой код так, как вам хотелось бы, и не использовать обратные вызовы.

Последнее преимущество событий: это очень простой способ соединения частей вашего кода вместе. Событие может быть отправлено, но если никакой код его не прослушивает, ничего страшного: оно будет просто пропущено незамеченным. Это означает, что удаление слушателей (или событий) никогда не приводит к ошибкам JavaScript.


Мы начнем с самого класса EventEmitter . Это довольно просто: нам нужен модуль событий:

Этот объект events имеет единственное свойство, которым является сам класс EventEmitter . Итак, давайте сделаем простой пример для начинающих:

Мы начинаем с создания нового объекта EventEmitter . Этот объект имеет два основных метода, которые мы используем для событий: on и emit .

Начнем с on . Этот метод принимает два параметра: мы начинаем с имени события, которое мы слушаем: в данном случае это "someEvent" . Но, конечно, это может быть что угодно, и вы обычно выбираете что-то лучше. Второй параметр — это функция, которая будет вызываться при возникновении события. Это все, что требуется для настройки события.

Теперь, чтобы запустить событие, вы передаете имя события в метод EventEmitter экземпляра emit . Это последняя строка кода выше. Если вы запустите этот код, вы увидите, что мы выводим текст на консоль.

Это самое основное использование EventEmitter . Вы также можете включить данные при стрельбе события:

Это только один параметр данных, но вы можете включить столько, сколько хотите. Чтобы использовать их в своей функции обработчика событий, просто возьмите их в качестве параметров:

Прежде чем продолжить, позвольте мне прояснить часть функциональности EventEmitter . Мы можем иметь более одного слушателя для каждого события; можно назначить несколько прослушивателей событий (все с on ), и все функции будут вызываться при запуске события. По умолчанию Node допускает до десяти слушателей на одно событие одновременно; если создано больше, узел выдаст предупреждение. Однако мы можем изменить эту сумму, используя setMaxListeners . Например, если вы запустите это, вы должны увидеть распечатанное предупреждение над выводом:

Чтобы установить максимальное количество зрителей, добавьте эту строку над слушателями:

Теперь, когда вы запустите его, вы не получите предупреждение.


Есть несколько других методов EventEmitter которые вы найдете полезными.

Вот аккуратный: once . Это как метод on , за исключением того, что он работает только один раз. После первого вызова слушатель удаляется.

Если вы запустите это, вы увидите сообщение только один раз. Второе излучение события не воспринимается любыми слушателями (и это нормально, кстати), потому что once слушатель был удален после использования один раз.

Говоря об удалении слушателей, мы можем сделать это вручную, вручную, несколькими способами. Во-первых, мы можем удалить одного слушателя с removeListener метода removeListener . Он принимает два параметра: имя события и функцию слушателя. До сих пор мы использовали анонимные функции в качестве наших слушателей. Если мы хотим иметь возможность удалить слушателя позже, это должна быть функция с именем, на которое мы можем ссылаться. Мы можем использовать этот метод removeListener для дублирования эффектов метода removeListener :

Если вы запустите это, вы увидите, что он имеет тот же эффект, что и once .

Если вы хотите удалить всех слушателей, связанных с данным событием, вы можете использовать removeAllListeners ; просто передайте название мероприятия:

Чтобы удалить всех слушателей для всех событий, вызовите функцию без каких-либо параметров.

Есть один последний метод: listener . Этот метод принимает имя события в качестве параметра и возвращает массив всех функций, которые прослушивают это событие. Вот пример этого, основанный на нашем примере onlyOnce :

Мы закончим этот раздел одним кусочком метаинности. Наш экземпляр EventEmitter фактически запускает два собственных события, которые мы можем прослушивать: одно, когда мы создаем новых слушателей, и одно, когда мы их удаляем. Посмотреть здесь:

Запустив это, вы увидите, что наши слушатели для новых и удаленных слушателей были запущены, и мы получили ожидаемые сообщения.

Итак, теперь, когда мы увидели все методы экземпляра EventEmitter , давайте посмотрим, как он работает в сочетании с другими модулями.

Поскольку класс EventEmitter представляет собой обычный JavaScript, имеет смысл использовать его в других модулях. Внутри ваших собственных модулей JavaScript вы можете создавать экземпляры EventEmitter и использовать их для обработки внутренних событий. Это все просто. Более интересно было бы создать модуль, который наследуется от EventEmitter , чтобы мы могли использовать его функциональную часть в открытом API.

На самом деле, есть встроенные модули Node, которые делают именно это. Например, вы можете быть знакомы с модулем http ; это модуль, который вы будете использовать для создания веб-сервера. Этот базовый пример показывает, как метод http.Server класса http.Server стал частью класса http.Server :

Если вы запустите этот фрагмент, процесс будет ожидать запроса; Вы можете перейти на http://localhost:3000 и вы получите ответ. Когда экземпляр сервера получает запрос от вашего браузера, он генерирует событие "request" событие, которое наш слушатель получит и сможет выполнить.

Итак, как мы можем создать класс, который будет наследоваться от EventEmitter ? На самом деле это не так сложно. Мы создадим простой класс UserList , который обрабатывает объекты пользователя. Итак, в файле userlist.js мы начнем с этого:

Нам нужен модуль util чтобы помочь с наследованием. Далее нам нужна база данных: вместо использования реальной базы данных мы просто будем использовать объект:

Теперь мы можем создать наш модуль. Если вы не знакомы с модулями Node, вот как они работают: по умолчанию любой JavaScript, который мы пишем внутри этого файла, доступен только для чтения изнутри файла. Если мы хотим сделать его частью общедоступного API модуля, мы сделаем его свойством module.exports или назначим совершенно новый объект или функцию для module.exports . Давай сделаем это:

Это функция конструктора, но это не ваша обычная функция конструктора JavaScript. Здесь мы используем метод call в конструкторе EventEmitter для запуска этого метода в новом объекте UserList ( this ). Если нам нужно выполнить любую другую инициализацию для нашего объекта, мы могли бы сделать это внутри этой функции, но это все, что мы сделаем на данный момент.

Наследование конструктора недостаточно; нам также нужно наследовать прототип. Это где модуль util входит.

Это добавит все, что есть в UserList.prototype в UserList.prototype ; теперь наши экземпляры UserList будут иметь все методы экземпляра EventEmitter . Но мы хотим добавить еще, конечно. Мы добавим метод save , чтобы позволить нам добавлять новых пользователей.

Этот метод берет объект для сохранения в нашей "database" : он добавляет id и помещает его в массив пользователей. Затем он генерирует событие "saved-user" и передает объект как данные. Если бы это была реальная база данных, сохранение ее, вероятно, было бы асинхронной задачей, то есть для работы с сохраненной записью нам нужно было бы принять обратный вызов. Альтернативой этому является создание события, как мы делаем. Теперь, если мы хотим что-то сделать с сохраненной записью, мы можем просто прослушать событие. Мы сделаем это через секунду. Давайте просто закроем UserList

Я добавил еще один метод: простой, который возвращает всех пользователей. Затем мы назначаем UserList для module.exports .

Теперь давайте посмотрим на это в использовании; в другом файле, скажем test.js Добавьте следующее:

После запроса нашего нового модуля и создания его экземпляра мы прослушиваем событие "saved-user" . Тогда мы можем пойти дальше и сохранить несколько пользователей. Когда мы запустим это, вы увидите, что мы получаем два сообщения, распечатывая имена и идентификаторы сохраненных нами записей.

Конечно, это может работать наоборот: мы можем использовать метод on внутри нашего класса и метод emit снаружи, либо оба изнутри или снаружи. Но это хороший пример того, как это можно сделать.


Вот как работает класс EventEmitter Node. Ниже вы найдете ссылки на документацию по Node для некоторых вещей, о которых мы говорили.