Добро пожаловать во вторую часть серии Изучите Node.js с Brigadier Fluffykins, которая поможет вам легко понять Node.js ❤

В Части I мы с Бригадиром Флаффикинсом рассказали о Node.js, о том, что с его помощью можно построить, а также о концепциях асинхронности и синхронизации. Я провел вас через установку, и вместе мы создали ваш первый сервер.

Это было великолепно:

Сегодняшний урок будет охватывать:

  • Почему Node.js - это язык, управляемый событиями, и как это важно для асинхронного поведения
  • Как события в DOM похожи на события в Node.js
  • Как цикл событий обрабатывает запросы
  • Создание настраиваемых событий с помощью EventEmitter

Событийно-ориентированное программирование - это круто

Поскольку Node.js является однопоточным, чтобы он мог создавать параллелизм и не был мучительно медленным - как в традиционной модели клиент-сервер, описанной в Части I, он использует события для прослушивания запросов.

Это отличается от, скажем, Apache, веб-сервера, использующего многопоточный HTTP. Для каждого запроса, который получает Apache, он создает новый поток для его обработки. Это означает, что да, хотя вы можете иметь более одного процесса, выполняемого одновременно через потоки, недостаток заключается в том, что результаты всех запросов должны возвращаться, прежде чем обслуживать страницу.

С другой стороны, управляемая событиями архитектура Node.j позволяет обрабатывать несколько запросов в одном потоке. Например, как только инициируется запрос событие, обратные вызовы и обещания обрабатывают эти запросы асинхронно.

Это означает, что если у вас поступает несколько запросов, а запрос A все еще выполняет свою работу, запрос B начнет получать результаты - результатом будет либо запрос B, который отвечает клиенту перед запросом A, либо одновременно с запросом A.

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

В модели параллелизма Node.js есть некоторые недостатки, но мы рассмотрим их в следующих нескольких уроках.

События в DOM похожи на события в Node.js

Подумайте о событиях так: так же, как события взаимодействуют с объектами DOM, многие объекты в Node.js испускают события.

Если вы выполняли какие-либо манипуляции с DOM с помощью JavaScript, вы понимаете, что DOM может иметь прослушиватели событий, такие как click, dblclick, submit, keydown, keyup и так далее. После запуска событие обрабатывается обратным вызовом.

Например, когда вы настраиваете событие click, у вас может быть обратный вызов, говорящий: «когда что-то щелкнули, сделайте третий div синим!»

Вот закодированный пример.

В вашем файле index.html:

В вашем файле main.js:

И, если вы хотите проверить это в своем собственном браузере, вот несколько CSS. Это должно быть в style.css:

Когда клиент нажимает кнопку, запускается наше событие click, и наш обратный вызов что-то делает с DOM. В этом случае третий div становится синим, а текст внутри кнопки меняется.

Как и в случае request event в Node.js, когда клиент нажимает кнопку, создается впечатление, что он отправляет запрос в файл main.js, где click прослушивает событие - точно так же, как событие request будет прослушивать входящие запросы.

Затем, точно так же, как событие response ответит клиенту некоторой информацией внутри обратного вызова, обратный вызов события click модели DOM отвечает изменением цвет фона третьего div. Он также изменяет текст на кнопке внутри html файла.

Основное различие между событиями в Node.js и событиями в DOM заключается в том, что события DOM остаются в основном привязанными к объекту DOM - на стороне клиента - в то время как события для Node.js больше ориентированы на отношения между клиентом и сервером. .

Node.js генерирует события от объектов, таких как объект веб-сервера (http.createServer). К счастью для вас, вы уже использовали события еще в Части I ШАГА №1.5!

На этом шаге вы сохранили объект веб-сервера в его собственной переменной и прослушивали входящие запросы через событие запроса, прикрепленное к объекту http.createServer в первом параметре.

Под этим объектом находится EventEmitter конструктор, о котором мы скоро узнаем. А пока просмотрите код, который мы создали в Части I, и убедитесь, что вы лучше понимаете, что происходит после нашего объяснения события.

Это снова для справки:

Цикл событий

Итак, у вас есть базовое представление о событиях и о том, как они связаны с Node.js, но как Node.js на самом деле работает под капотом?

Первое, что делает Node.js при чтении вашего кода - это события подписки, которые вы использовали, такие как request, listen, connection или закрыть. После этого он переходит в цикл событий и постоянно прослушивает эти события внутри одного потока.

Например, на сервере, который мы ранее создали выше, он только прослушивает событие запроса, и поэтому цикл событий думает:

«Были ли запросы?»

"Как насчет сейчас?"

“…. “

"Теперь?"

"Теперь верно?"

Не беспокойтесь, однопоточный цикл событий Node.js - это не Жюль Винфилд. На самом деле он просто терпеливо ждет и прислушивается к событиям, на которые ранее был подписан в фоновом режиме.

Если запрос все-таки поступает, он запускает событие request и запускает обратный вызов, который мы написали - в нашем случае mini html внутри метода end в нашем предыдущем примере сервера. Также имейте в виду, что события могут запускать другие события.

Но что, если одновременно поступает несколько запросов? Нравится событие request и close? Цикл событий будет обрабатывать эти события по одному. Итак, сначала будет обработано запрос событие, а затем close событие. Пока они обрабатываются, они не блокируют поступление дополнительных событий. Если бы они это сделали, наш код работал бы вдвое дольше.

Давайте углубимся в то, что все это означает

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

Если бы JavaScript был синхронным языком, и к нам поступало бы два запроса, как вы думаете, что бы произошло? Нам пришлось бы дождаться результата первого запроса, прежде чем мы сможем обработать второй запрос. Это означает, что первый запрос останется в стеке вызовов, блокируя поступление любых других запросов до тех пор, пока не будут получены необходимые результаты.

После получения результатов первый запрос «выскакивает», и только после этого второй запрос попадает в стек вызовов и выполняется:

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

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

Когда асинхронная функция с обратным вызовом или событием попадает в стек вызовов, она автоматически перемещается в веб-API. Веб-API - это место, где хранятся события, подписанные на цикл событий. Они ожидают приказов от Event Loop, который отслеживает, вызваны ли какие-либо события.

Как только кто-то инициирует событие, например событие запроса, обратный вызов этого события отправляется в очередь событий. Эта очередь также называется очередью обратного вызова или просто очередью задач.

Причина, по которой у нас есть несколько имен для очереди, заключается в том, что тот же процесс, который происходит для событий, происходит для асинхронных функций - или методов - всего, что имеет обратный вызов, включая события DOM и функции событий, не являющиеся частью собственного JavaScript, например ajax и setTimeout (да, они являются частью веб-API, а не JavaScript).

Теперь последнее, что происходит, - это обратный вызов события запроса, который будет ждать внутри этой очереди событий / обратных вызовов / задач, пока стек вызовов не пустой. Это как-то связано с тем, как JavaScript обрабатывает память - так что в основном просто знайте, как только мы дойдем до этого этапа, нам нужно подождать, пока все функции по-прежнему будут пусты, прежде чем мы сможем добавить обратный вызов в стек вызовов и обработайте это.

Вот наглядная демонстрация всего, что мы только что объяснили:

  1. JavaScript сканирует ваш код и складывает функции, события и все остальное в стек вызовов.
  2. Золотые полосы ниже - это обычные, не асинхронные функции. Последние розовая и зеленая полоски - это два события запроса. Эти события подписываются на Event Loop (играет Бригадир Флаффикинс) и ожидают вызова внутри Web API.
  3. Пока события ожидают, другие функции выполняются в стеке вызовов.
  4. Как только событие запускается, Event Loop слышит его, и обратный вызов этого конкретного события перемещается в очередь . Хотя , поскольку это событие запроса, оно сначала будет ждать любых результатов, которые ему нужны. И только после этого он отправляет обратный вызов в очередь.
  5. Хотя в стеке вызовов все еще работают и выполняются функции, события должны ждать, пока стек вызовов не опустеет, чтобы они могли запуститься. Бригадный генерал Флаффикинс сообщает им, можно ли переходить в стек вызовов или нет, в зависимости от того, пуст он или нет.

Давайте создавать собственные события!

Генераторы событий широко используются в библиотеках Node.js, поэтому давайте научимся создавать свои собственные и лучше поймем, как они работают!

Все объекты, генерирующие события, являются экземплярами EventEmitter класса, и все события наследуются от конструктора EventEmitter. Мы создадим два события для эмиттера событий bunnyError - bunnyWarning и bunnyNeed.

Скопируйте и вставьте это в файл с именем bunnyEmitter.js:

Хорошо, так что здесь происходит?

Сначала нам нужен объект EventEmitter в Node.js, а затем мы создаем экземпляр нового объекта EventEmitter, для которого мы будем создавать настраиваемые события. Мы называем этот экземпляр bunnyError.

Затем мы создаем прослушиватель событий для нашего первого события, bunnyWarning, с помощью метода on, который прослушивает событие. Мы обрабатываем это событие, когда оно используется, вызывая обратный вызов, который просто печатает «BUNNY WARNING: warning».

Обратите внимание, что я использовал шаблонные литералы - функцию ES6. Вы можете узнать больше о них здесь. Это то же самое, что сказать console.log (BUNNY WARNING: + message).

Наконец, мы используем метод emit для запуска или вызова события. После вызова события должен выполняться обратный вызов. Мы можем делать это столько раз, сколько захотим.

Предполагая, что файл находится на вашем рабочем столе, введите в оболочке node bunnyEmitter.js:

Если вы хотите, чтобы эмиттер событий запускался только один раз, объект EventEmitter имеет метод с именем .once, который можно использовать вместо .on:

yourEventEmitter.once(yourEvent, yourCallback)

При этом независимо от того, сколько раз вы запускаете yourEvent, он будет работать только один раз.

Рекомендуется ограничить количество имеющихся слушателей событий. Фактически, если у вас их больше десяти, вы получите предупреждение:

"(node) warning: possible EventEmitter memory leak detected. 11 listeners added. Use emitter.setMaxListeners() to increase limit."

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

прослушиватель событий - это событие, которое вы создаете для прослушивания любых входящих событий. обработчик событий - это обратный вызов, который запускается, когда прослушиватель событий слышит событие.

В нашем примере генератора настраиваемых событий прослушивателями событий были bunnyWarning и bunnyNeeds, а обработчики событий были обратными вызовами каждое событие.

Ознакомьтесь с этими дополнительными ресурсами

Поздравляю! Вы прошли через Изучение Node.js с Бригадиром Флаффикинсом, часть 2! На сегодняшнем уроке вы узнали, что Node.js - это язык, управляемый событиями, и почему это полезно для асинхронного поведения. Вы также узнали, как эти события обрабатываются с помощью цикла событий.

Мы также погрузились в изучение сходства между событиями DOM и событиями в Node.js, чтобы помочь вам немного глубже погрузиться в эту новую сферу.

Наконец, мы создали первый EventEmitter и два потрясающих события!

Давайте узнаем больше об этих темах, а также о других, которые мы только рассмотрели в следующих нескольких уроках. Спасибо за чтение и следите за обновлениями.

Держите свою мудрость в курсе, нажав ❤ ниже и подписавшись, так как на Medium скоро появится больше Learn Node.js With Brigadier Fluffykins!

Часть I. Синхронизация, асинхронизация и создание вашего первого сервера!

Часть II: События, EventEmitter и Event Loop

Часть III: запрос объекта, настройка маршрутов, обслуживание файлов