Рубрики
Без рубрики

Цикл событий JavaScript

Цикл событий является одним из наиболее важных аспектов для понимания JavaScript. Этот пост объясняет это простыми словами

  • Вступление
  • Блокировка цикла событий
  • Стек вызовов
  • Простое объяснение цикла событий
  • Выполнение функции очереди
  • Очередь Сообщений
  • Очередь заданий ES6

Вступление

Цикл событий является одним из наиболее важных аспектов для понимания JavaScript.

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

Этот пост призван объяснить внутренние детали того, как JavaScript работает с одним потоком и как он обрабатывает асинхронные функции.

Ваш код JavaScript выполняется в одном потоке. За раз происходит только одно событие.

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

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

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

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

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

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

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

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

Стек вызовов

Стек вызовов представляет собой очередь LIFO (Последний вход, Первый выход).

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

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

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

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

Давайте возьмем пример:

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

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() .

На данный момент стек вызовов выглядит следующим образом:

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

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

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

Приведенный выше пример выглядит нормально, в нем нет ничего особенного: 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().

На данный момент стек вызовов выглядит следующим образом:

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

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

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

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

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

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

Нам не нужно ждать, пока такие функции, как 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

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

Оригинал: “https://flaviocopes.com/javascript-event-loop/”