Хабрахабр

Что ты такое, Event Loop? Или как устроен цикл событий в браузере Chrome

Как думаете, что произойдет, если запустить в консоли браузера этот фрагмент кода?

function foo() { setTimeout(foo, 0);
} foo();

А этот?

function foo() { Promise.resolve().then(foo);
} foo();

Если вы также, как и я, прочитали кучу статей про Event Loop, Main Thread, таски, микротаски и прочее, но затрудняетесь ответить на вопросы выше — эта статья для вас.
Итак, приступим. Код каждой HTML-страницы в браузере выполняется в Main Thread. Main Thread — это основной поток, где браузер выполняет JS, делает перерисовки, обрабатывает пользовательские действия и многое другое. По сути, это то место, где движок JS интегрирован в браузер.

Проще всего разобраться, глядя на схему:


Рисунок 1

Представьте, что вы оказались на его месте. Мы видим, что единственное место, через которое задачи могут попасть в Call Stack и выполниться — это Event Loop. Задачи могут быть двух типов: И ваша работа успевать 'разгребать' задачи.

  1. Личные — выполнение основного JavaScript-кода на сайте (далее будем считать, что он уже выполнился)
  2. Задачи от заказчиков — Render, Microtasks и Tasks

Скорее всего, личные задачи у вас будут приоритетнее. Event Loop с этим согласен 🙂 Остается упорядочить задачи от заказчика.

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

Взглянем на эту схему:


Рисунок 2

После того как Call Stack станет пустым (закончились личные задачи), Event Loop первым делом идет к заказчику Tasks и просит у него только одну, первую в очереди задачу, передает ее в CallStack и идет дальше. На основе этой схемы строится вся работа Event Loop. У него Event Loop берет задачи до тех пор, пока они не закончатся. Следующий заказчик — Microtasks. Задачи от Render оптимизируются браузером и, если он посчитает, что в этом цикле не нужно ничего перерисовывать, то Event Loop просто пойдет дальше. Это значит, что если время их добавления равно времени их выполнения, то Event Loop будет бесконечно их разгребать.
Далее он идет к Render и выполняет задачи от него.

После Render цикл снова повторяется и так пока вкладка браузера не закроется и не завершится процесс.

И, наоборот, если у заказчика задачи занимают много времени, то остальные заказчики будут ждать своей очереди. Если у кого-то из заказчиков не оказалось задач, то Event Loop просто идет к следующему. А если задачи от какого-то заказчика оказались бесконечными, то Call Stack переполняется, и браузер начинает ругаться:


Рисунок 3

Теперь, когда мы поняли как работает Event Loop, пришло время разобраться, что будет после выполнения фрагментов кода в начале этой статьи.

function foo() { setTimeout(foo, 0);
} foo();

Мы видим, что функция foo вызывает сама себя рекурсивно через setTimeout внутри, но при каждом вызове она создает задачу заказчика Tasks. Как мы помним, в цикле Event Loop при выполнении очереди задач от Tasks берет только 1 задачу в цикл. И далее происходит выполнение задач от Microtasks и Render. Поэтому этот фрагмент кода не заставит Event Loop страдать и вечно разгребать его задачи. Но будет подкидывать новую задачу для заказчика Tasks на каждом круге.

Для этого я создал простой HTML-документ и подключил в нем script.js с этим фрагментом кода. Давайте попробуем выполнить этот скрипт в браузере Google Chrome. После открытия документа заходим в инструменты разработчика, и открываем вкладку Perfomance и жмем там кнопку 'start profiling and reload page':


Рисунок 4

Видим, что задачи от Tasks выполняются по одной в цикл, примерно раз в 4ms.

Рассмотрим вторую задачку:

function foo() { Promise.resolve().then(foo);
} foo();

Здесь мы видим тоже самое, что и в примере выше, но вызов foo добавляет задачи от Microtasks, а они выполняются все, пока не закончатся. А это значит, что пока Event Loop не закончит их, перейти к следующему заказчику он не сможет 🙁 И мы видим снова грустную картинку.

Взглянем на это в интрументах разработчкика:


Рисунок 5

1ms, и это в 40 раз быстрее, чем очередь Tasks. Мы видим, что микротаски выполняются примерно раз в 0. В нашем примере очередь движется бесконечно. Все потому, что они выполняются все и сразу. Для визуализации я уменьшил ее до 100 000 итераций.

Вот и все!

Надеюсь, эта статья была вам полезной, и теперь вы понимаете, как работает Event Loop, и что 'творится' в примерах кода выше.

Если вам понравилось, ставьте лайки и подписывайтесь на мой канал 🙂 Всем пока 🙂 И до новых встреч.

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть