Когда я впервые услышал о Node.js , я подумал, что это просто реализация JavaScript для сервера. Но на самом деле это гораздо больше: он поставляется с множеством встроенных функций, которые вы не получаете в браузере. Одной из таких функциональных возможностей является модуль Event, который имеет класс EventEmitter
. Мы рассмотрим это в этом уроке.
EventEmitter
: что и почему
Итак, что именно делает класс EventEmitter
? Проще говоря, он позволяет вам прослушивать «события» и назначать действия, выполняемые при возникновении этих событий. Если вы знакомы с интерфейсным JavaScript, вы будете знать о событиях мыши и клавиатуры, которые происходят при определенных взаимодействиях с пользователем. Они очень похожи, за исключением того, что мы можем генерировать события самостоятельно, когда мы этого хотим, и не нужны на основе взаимодействия с пользователем. Принципы, на которых основан EventEmitter
, называются моделью публикации / подписки, потому что мы можем подписаться на события и затем публиковать их. Существует много интерфейсных библиотек, созданных с поддержкой pub / sub, но в Node это встроено.
Другой важный вопрос: зачем вам использовать модель событий? В Node это альтернатива глубоко вложенным обратным вызовам. Многие методы Node запускаются асинхронно, что означает, что для запуска кода после завершения метода необходимо передать метод обратного вызова в функцию. Со временем ваш код будет выглядеть как гигантская воронка. Чтобы предотвратить это, многие классы узлов генерируют события, которые вы можете прослушивать. Это позволяет вам организовать свой код так, как вам хотелось бы, и не использовать обратные вызовы.
Последнее преимущество событий: это очень простой способ соединения частей вашего кода вместе. Событие может быть отправлено, но если никакой код его не прослушивает, ничего страшного: оно будет просто пропущено незамеченным. Это означает, что удаление слушателей (или событий) никогда не приводит к ошибкам JavaScript.
Использование EventEmitter
Мы начнем с самого класса EventEmitter
. Это довольно просто: нам нужен модуль событий:
var events = require ("events");
Этот объект events
имеет единственное свойство, которым является сам класс EventEmitter
. Итак, давайте сделаем простой пример для начинающих:
var EventEmitter = require ("events"). EventEmitter; var ee = new EventEmitter (); ee.on ("someEvent", function () { console.log («событие произошло»); }); ee.emit ( "someEvent");
Мы начинаем с создания нового объекта EventEmitter
. Этот объект имеет два основных метода, которые мы используем для событий: on
и emit
.
Начнем с on
. Этот метод принимает два параметра: мы начинаем с имени события, которое мы слушаем: в данном случае это "someEvent"
. Но, конечно, это может быть что угодно, и вы обычно выбираете что-то лучше. Второй параметр — это функция, которая будет вызываться при возникновении события. Это все, что требуется для настройки события.
Теперь, чтобы запустить событие, вы передаете имя события в метод EventEmitter
экземпляра emit
. Это последняя строка кода выше. Если вы запустите этот код, вы увидите, что мы выводим текст на консоль.
Это самое основное использование EventEmitter
. Вы также можете включить данные при стрельбе события:
ee.emit ("новый пользователь", userObj);
Это только один параметр данных, но вы можете включить столько, сколько хотите. Чтобы использовать их в своей функции обработчика событий, просто возьмите их в качестве параметров:
ee.on ("новый пользователь", функция (данные) { // использовать данные здесь });
Прежде чем продолжить, позвольте мне прояснить часть функциональности EventEmitter
. Мы можем иметь более одного слушателя для каждого события; можно назначить несколько прослушивателей событий (все с on
), и все функции будут вызываться при запуске события. По умолчанию Node допускает до десяти слушателей на одно событие одновременно; если создано больше, узел выдаст предупреждение. Однако мы можем изменить эту сумму, используя setMaxListeners
. Например, если вы запустите это, вы должны увидеть распечатанное предупреждение над выводом:
ee.on ("someEvent", function () {console.log ("event 1");}); ee.on ("someEvent", function () {console.log ("event 2");}); ee.on ("someEvent", function () {console.log ("event 3");}); ee.on ("someEvent", function () {console.log ("event 4");}); ee.on ("someEvent", function () {console.log ("event 5");}); ee.on ("someEvent", function () {console.log ("event 6");}); ee.on ("someEvent", function () {console.log ("event 7");}); ee.on ("someEvent", function () {console.log ("event 8");}); ee.on ("someEvent", function () {console.log ("event 9");}); ee.on ("someEvent", function () {console.log ("event 10");}); ee.on ("someEvent", function () {console.log ("event 11");}); ee.emit ( "someEvent");
Чтобы установить максимальное количество зрителей, добавьте эту строку над слушателями:
ee.setMaxListeners (20);
Теперь, когда вы запустите его, вы не получите предупреждение.
Другие методы EventEmitter
Есть несколько других методов EventEmitter
которые вы найдете полезными.
Вот аккуратный: once
. Это как метод on
, за исключением того, что он работает только один раз. После первого вызова слушатель удаляется.
ee.once ("firstConnection", function () {console.log ("Вы никогда больше этого не увидите");}); ee.emit ( "firstConnection"); ee.emit ( "firstConnection");
Если вы запустите это, вы увидите сообщение только один раз. Второе излучение события не воспринимается любыми слушателями (и это нормально, кстати), потому что once
слушатель был удален после использования один раз.
Говоря об удалении слушателей, мы можем сделать это вручную, вручную, несколькими способами. Во-первых, мы можем удалить одного слушателя с removeListener
метода removeListener
. Он принимает два параметра: имя события и функцию слушателя. До сих пор мы использовали анонимные функции в качестве наших слушателей. Если мы хотим иметь возможность удалить слушателя позже, это должна быть функция с именем, на которое мы можем ссылаться. Мы можем использовать этот метод removeListener
для дублирования эффектов метода removeListener
:
function onlyOnce () { console.log («Вы никогда этого больше не увидите»); ee.removeListener ("firstConnection", onlyOnce); } ee.on ("firstConnection", onlyOnce) ee.emit ( "firstConnection"); ee.emit ( "firstConnection");
Если вы запустите это, вы увидите, что он имеет тот же эффект, что и once
.
Если вы хотите удалить всех слушателей, связанных с данным событием, вы можете использовать removeAllListeners
; просто передайте название мероприятия:
ee.removeAllListeners ( "firstConnection");
Чтобы удалить всех слушателей для всех событий, вызовите функцию без каких-либо параметров.
ee.removeAllListeners ();
Есть один последний метод: listener
. Этот метод принимает имя события в качестве параметра и возвращает массив всех функций, которые прослушивают это событие. Вот пример этого, основанный на нашем примере onlyOnce
:
function onlyOnce () { console.log (ee.listeners ( "firstConnection")); ee.removeListener ("firstConnection", onlyOnce); console.log (ee.listeners ( "firstConnection")); } ee.on ("firstConnection", onlyOnce) ee.emit ( "firstConnection"); ee.emit ( "firstConnection");
Мы закончим этот раздел одним кусочком метаинности. Наш экземпляр EventEmitter
фактически запускает два собственных события, которые мы можем прослушивать: одно, когда мы создаем новых слушателей, и одно, когда мы их удаляем. Посмотреть здесь:
ee.on ("newListener", функция (evtName, fn) { console.log («Новый слушатель:» + evtName); }); ee.on ("removeListener", function (evtName) { console.log («Удаленный слушатель:» + evtName); }); function foo () {} ee.on ("save-user", foo); ee.removeListener ("save-user", foo);
Запустив это, вы увидите, что наши слушатели для новых и удаленных слушателей были запущены, и мы получили ожидаемые сообщения.
Итак, теперь, когда мы увидели все методы экземпляра EventEmitter
, давайте посмотрим, как он работает в сочетании с другими модулями.
EventEmitter
внутри модулей
Поскольку класс EventEmitter
представляет собой обычный JavaScript, имеет смысл использовать его в других модулях. Внутри ваших собственных модулей JavaScript вы можете создавать экземпляры EventEmitter
и использовать их для обработки внутренних событий. Это все просто. Более интересно было бы создать модуль, который наследуется от EventEmitter
, чтобы мы могли использовать его функциональную часть в открытом API.
На самом деле, есть встроенные модули Node, которые делают именно это. Например, вы можете быть знакомы с модулем http
; это модуль, который вы будете использовать для создания веб-сервера. Этот базовый пример показывает, как метод http.Server
класса http.Server
стал частью класса http.Server
:
var http = require ("http"); var server = http.createServer (); server.on ("запрос", функция (req, res) { res.end («это ответ»); }); server.listen (3000);
Если вы запустите этот фрагмент, процесс будет ожидать запроса; Вы можете перейти на http://localhost:3000
и вы получите ответ. Когда экземпляр сервера получает запрос от вашего браузера, он генерирует событие "request"
событие, которое наш слушатель получит и сможет выполнить.
Итак, как мы можем создать класс, который будет наследоваться от EventEmitter
? На самом деле это не так сложно. Мы создадим простой класс UserList
, который обрабатывает объекты пользователя. Итак, в файле userlist.js
мы начнем с этого:
var util = require ("util"); var EventEmitter = require ("events"). EventEmitter;
Нам нужен модуль util
чтобы помочь с наследованием. Далее нам нужна база данных: вместо использования реальной базы данных мы просто будем использовать объект:
var id = 1; база данных var = { пользователи: [ {id: id ++, имя: "Джо Смит", род занятий: "разработчик"}, {id: id ++, имя: "Джейн Доу", род занятий: "аналитик данных"}, {id: id ++, имя: "Джон Генри", род деятельности: "дизайнер"} ] };
Теперь мы можем создать наш модуль. Если вы не знакомы с модулями Node, вот как они работают: по умолчанию любой JavaScript, который мы пишем внутри этого файла, доступен только для чтения изнутри файла. Если мы хотим сделать его частью общедоступного API модуля, мы сделаем его свойством module.exports
или назначим совершенно новый объект или функцию для module.exports
. Давай сделаем это:
function UserList () { EventEmitter.call (это); }
Это функция конструктора, но это не ваша обычная функция конструктора JavaScript. Здесь мы используем метод call
в конструкторе EventEmitter
для запуска этого метода в новом объекте UserList
( this
). Если нам нужно выполнить любую другую инициализацию для нашего объекта, мы могли бы сделать это внутри этой функции, но это все, что мы сделаем на данный момент.
Наследование конструктора недостаточно; нам также нужно наследовать прототип. Это где модуль util
входит.
util.inherits (UserList, EventEmitter);
Это добавит все, что есть в UserList.prototype
в UserList.prototype
; теперь наши экземпляры UserList
будут иметь все методы экземпляра EventEmitter
. Но мы хотим добавить еще, конечно. Мы добавим метод save
, чтобы позволить нам добавлять новых пользователей.
UserList.prototype.save = function (obj) { obj.id = id ++; database.users.push (OBJ); this.emit ("сохраненный пользователь", объект); };
Этот метод берет объект для сохранения в нашей "database"
: он добавляет id
и помещает его в массив пользователей. Затем он генерирует событие "saved-user"
и передает объект как данные. Если бы это была реальная база данных, сохранение ее, вероятно, было бы асинхронной задачей, то есть для работы с сохраненной записью нам нужно было бы принять обратный вызов. Альтернативой этому является создание события, как мы делаем. Теперь, если мы хотим что-то сделать с сохраненной записью, мы можем просто прослушать событие. Мы сделаем это через секунду. Давайте просто закроем UserList
UserList.prototype.all = function () { вернуть database.users; }; module.exports = UserList;
Я добавил еще один метод: простой, который возвращает всех пользователей. Затем мы назначаем UserList
для module.exports
.
Теперь давайте посмотрим на это в использовании; в другом файле, скажем test.js
Добавьте следующее:
var UserList = require ("./ userlist"); var users = new UserList (); users.on ("сохраненный пользователь", функция (пользователь) { console.log ("сохранено:" + user.name + "(" + user.id + ")"); }); users.save ({имя: «Джейн Доу», род занятий: «менеджер»}); users.save ({имя: «Джон Джейкоб», род занятий: «разработчик»});
После запроса нашего нового модуля и создания его экземпляра мы прослушиваем событие "saved-user"
. Тогда мы можем пойти дальше и сохранить несколько пользователей. Когда мы запустим это, вы увидите, что мы получаем два сообщения, распечатывая имена и идентификаторы сохраненных нами записей.
спас: Джейн Доу (4) спас: Джон Джейкоб (5)
Конечно, это может работать наоборот: мы можем использовать метод on
внутри нашего класса и метод emit
снаружи, либо оба изнутри или снаружи. Но это хороший пример того, как это можно сделать.
Вывод
Вот как работает класс EventEmitter
Node. Ниже вы найдете ссылки на документацию по Node для некоторых вещей, о которых мы говорили.
- Модуль событий узла
- Узел Util Модуль
- Узел HTTP Agent Source — показывает шаблон наследования, который мы использовали.