Весной стартует сезон найма, успей отхватить свой оффер!

Как работает Event Loop в JavaScript?

Event Loop — это механизм в JavaScript, который управляет асинхронными задачами и очередями событий. Он позволяет JavaScript работать в однопоточной модели, обрабатывая асинхронные операции, не блокируя основной поток выполнения.

Как работает Event Loop?

Event Loop — это бесконечный цикл, в котором выполняются обработчики событий. В процессе его работы браузер распределяет задачи по двум основным очередям:

  • Микротаски — это задачи, такие как промисы, queueMicrotask(), MutationObserver.
  • Макротаски — это задачи, такие как setTimeout, события, таймеры, запросы fetch.

Основной порядок выполнения

  1. Сначала выполняются все синхронные задачи из стека вызовов (Call Stack).
  2. Затем выполняются все задачи из очереди микротасков. Все микротаски будут выполнены до того, как будет взята следующая макрозадача. Микротаски блокируют Event Loop, пока все не будут выполнены.
  3. После выполнения всех микротасков очередь очищается.
  4. Далее из очереди макротасков берется одна макрозадача и выполняется. Макрозадачи, такие как setTimeout, ждут, пока все синхронные задачи и микротаски будут завершены.
  5. После выполнения макрозадачи проверяется, нужно ли выполнить перерисовку страницы. Если необходимо, браузер выполняет рендеринг.
  6. Повторный цикл. Event Loop продолжает обрабатывать задачи, пока не закончится выполнение всех операций.

Структуры данных

  1. Call Stack (LIFO — Last In, First Out):

    • Стек вызовов содержит все функции, которые выполняются в текущий момент.
    • Когда вызывается функция, она добавляется в стек. Если внутри функции вызывается другая функция, она тоже добавляется в стек.
    • После выполнения функции она удаляется из стека.
  2. Web API:

    • Асинхронные операции, такие как setTimeout, обработчики событий, запросы fetch, попадают в Web API.
    • После завершения работы асинхронной задачи, она не сразу попадает в Call Stack. Вместо этого она помещается в Callback Queue (очередь обратных вызовов).
  3. Callback Queue (FIFO — First In, First Out):

    • Здесь хранятся функции, которые должны быть выполнены после того, как стек вызовов окажется пустым.
    • Когда стек вызовов очищается, Event Loop перемещает задачи из Callback Queue в Call Stack и выполняет их.

Микротаски vs. Макротаски

  • Макротаски:

    • Пример: setTimeout, события (onClick, onChange), запросы fetch.
    • Эти задачи обрабатываются в порядке очереди, одна за другой, после того как выполняются все синхронные задачи и микротаски.
  • Микротаски:

    • Пример: промисы, queueMicrotask(), MutationObserver.
    • Микротаски выполняются сразу после выполнения синхронных операций и до выполнения макротасок.
    • Все микротаски из очереди выполняются перед следующими макрозадачами.

Пример

console.log("Первое сообщение");

setTimeout(() => {
  console.log("Второе сообщение");
}, 0);

Promise.resolve().then(() => console.log("Третье сообщение"));

console.log("Четвертое сообщение");

Что произойдет:

  • Первое сообщение выводится первым, потому что это синхронная операция.
  • setTimeout помещает свою задачу в очередь макротасок, но она не будет выполнена, пока не будут обработаны все микротаски.
  • Promise.resolve().then(...) помещает задачу в очередь микротасок и будет выполнена перед следующей макрозадачей.
  • Четвертое сообщение выводится следующим, так как это синхронный код.
  • После завершения синхронного кода, микротаск выполняется (выводится Третье сообщение).
  • Второе сообщение выводится последним, так как это макротаска и она выполняется только после всех микротасков.

Важное замечание:

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