Цикл событий Node.js

Цикл событий - один из наиболее важных аспектов работы Node.

Вступление

ВЦикл событий- один из самых важных аспектов, которые нужно понять о Node.

Почему это так важно? Потому что он объясняет, как Node может быть асинхронным и иметь неблокирующий ввод-вывод, и поэтому в основном объясняет «убийственное приложение» Node, то, что сделало его таким успешным.

Код JavaScript для Node.js выполняется в одном потоке. Одновременно происходит только одно событие.

Это ограничение, которое на самом деле очень полезно, поскольку оно значительно упрощает программирование, не беспокоясь о проблемах параллелизма.

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

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

Среда управляет несколькими параллельными циклами событий, например, для обработки вызовов API.Веб-воркерытакже запускаются в собственном цикле событий.

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

Блокировка цикла событий

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

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

Стек вызовов

Стек вызовов представляет собой очередь LIFO (Last In, First Out).

Цикл событий постоянно проверяетстек вызововчтобы узнать, нужно ли запускать какую-либо функцию.

При этом он добавляет любой найденный вызов функции в стек вызовов и выполняет каждый из них по порядку.

Вы знаете трассировку стека ошибок, с которой вы, возможно, знакомы, в отладчике или в консоли браузера? Браузер просматривает имена функций в стеке вызовов, чтобы сообщить вам, какая функция инициирует текущий вызов:

Exception call stack

Простое объяснение цикла событий

Возьмем пример:

я используюfoo,barиbazв качествеслучайные имена. Введите любое имя, чтобы заменить их.

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) bar() baz() }

foo()

Этот код печатает

foo
bar
baz

как и ожидалось.

Когда этот код запускается, сначалаfoo()называется. Внутриfoo()мы сначала звонимbar(), затем мы звонимbaz().

На данный момент стек вызовов выглядит так:

Call stack first example

Цикл событий на каждой итерации проверяет, есть ли что-то в стеке вызовов, и выполняет это:

Execution order first example

пока стек вызовов не станет пустым.

Выполнение функции очереди

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

Давайте посмотрим, как отложить выполнение функции до тех пор, пока стек не очистится.

Вариант использованияsetTimeout(() => {}), 0)заключается в том, чтобы вызвать функцию, но выполнить ее после того, как выполнятся все остальные функции в коде.

Возьмем этот пример:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) baz() }

foo()

Этот код печатает, может быть, неожиданно:

foo
baz
bar

Когда этот код запускается, сначала вызывается foo (). Внутри foo () мы сначала вызываем setTimeout, передаваяbarв качестве аргумента, и мы инструктируем его немедленно запускаться как можно быстрее, передавая 0 в качестве таймера. Затем мы вызываем baz ().

На данный момент стек вызовов выглядит так:

Call stack second example

Вот порядок выполнения всех функций в нашей программе:

Execution order second example

Почему это происходит?

Очередь сообщений

Когда вызывается setTimeout (), браузер или Node.js запускаюттаймер. По истечении таймера, в данном случае сразу после того, как мы установили 0 в качестве тайм-аута, функция обратного вызова помещается вОчередь сообщений.

В очереди сообщений также находятся инициированные пользователем события, такие как события щелчка или клавиатуры, илипринестиответы ставятся в очередь до того, как ваш код сможет на них отреагировать. Или такжеДОМтакие события, какonLoad.

Цикл отдает приоритет стеку вызовов, и сначала он обрабатывает все, что находит в стеке вызовов, а когда там ничего нет, он переходит к подбору вещей из очереди сообщений.

Нам не нужно ждать таких функций, какsetTimeout, fetch или другие вещи для выполнения своей работы, потому что они предоставляются браузером и живут в своих собственных потоках. Например, если вы установитеsetTimeoutтайм-аут до 2 секунд, вам не нужно ждать 2 секунды - ожидание происходит в другом месте.

Очередь заданий ES6

ECMAScript 2015представила концепцию очереди заданий, которая используется Promises (также представленная в ES6 / ES2015). Это способ как можно скорее выполнить результат асинхронной функции, а не помещать его в конец стека вызовов.

Обещания, которые выполняются до завершения текущей функции, будут выполнены сразу после текущей функции.

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

Пример:

const bar = () => console.log('bar')

const baz = () => console.log(‘baz’)

const foo = () => { console.log(‘foo’) setTimeout(bar, 0) new Promise((resolve, reject) => resolve(‘should be right after baz, before bar’) ).then(resolve => console.log(resolve)) baz() }

foo()

Это печатает

foo
baz
should be right after baz, before bar
bar

В этом большая разница между Promises (и Async / await, который построен на обещаниях) и простыми старыми асинхронными функциями черезsetTimeout()или API других платформ.

Вывод

Эта статья познакомила вас с основными строительными блоками цикла событий Node.js.

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

Скачать мою бесплатнуюСправочник по Node.js


Дополнительные руководства по узлам: