Перейти к содержимому

Понимание Event Loop, микрозадач и макрозадач в JavaScript: руководство для начинающих

JavaScript — это мощный язык, часто используемый в веб-разработке, и одним из его ключевых концептов является обработка асинхронных операций. Чтобы сделать эту концепцию понятнее для начинающих, мы рассмотрим Event Loop, а также роли микрозадач и макрозадач, сопроводив всё простыми примерами, демонстрирующими, как JavaScript управляет различными операциями.

Упрощённый обзор Event Loop

Event Loop — это механизм, который JavaScript использует для эффективной обработки выполнения кода, событий и сообщений. Вот простой способ визуализировать его работу:

  1. Стек (Stack): Здесь начинается выполнение вашего JavaScript-кода, сверху вниз, подобно укладке блоков.
  2. Куча (Heap): Представьте это как хранилище памяти для объектов, создаваемых вашим кодом.
  3. Очередь (Queue): JavaScript на самом деле использует две отдельные очереди: очередь макрозадач (для таймеров, событий UI и ввода-вывода) и очередь микрозадач (для промисов и queueMicrotask).

Event Loop постоянно проверяет стек на наличие задач, требующих выполнения. Если стек пуст, он сначала проверяет очередь микрозадач. Как только очередь микрозадач опустеет, он обращается к очереди макрозадач, чтобы проверить наличие ожидающих задач для выполнения. Этот цикл позволяет JavaScript выполнять такие задачи, как обновление интерфейса, обработка взаимодействий с пользователем или фоновая загрузка данных, не блокируя основной поток.

Here's a simple example to explain how the JavaScript event loop works, particularly focusing on handling asynchronous events with setTimeout:


Output appears here after Run.

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

  1. console.log('Start'); выполняется первым, выводя «Start» в консоль.
  2. setTimeout планирует выполнение функции обратного вызова через 1000 миллисекунд (1 секунду). При этом он не блокирует выполнение последующего кода.
  3. console.log('End'); выполняется сразу после установки тайм-аута, выводя «End» в консоль.
  4. После того как основной поток освободится и пройдёт 1 секунда задержки, Event Loop сначала проверяет очередь микрозадач. Если она пуста, он переходит к очереди макрозадач, находит обратный вызов из setTimeout и выполняет его, выводя «Timeout Callback» в консоль.

Эта последовательность наглядно демонстрирует, как Event Loop в JavaScript обрабатывает задачи и использует очередь задач для управления асинхронными событиями, такими как тайм-ауты. Обратный вызов setTimeout является примером макрозадачи, которая обрабатывается после выполнения текущего скрипта и всех остальных ожидающих задач. Это гарантирует, что синхронный код выполняется немедленно, без ожидания асинхронного кода (например, операций ввода-вывода или таймеров), что поддерживает отзывчивость приложения.

Микрозадачи против макрозадач

Что такое макрозадачи?

Макрозадачи — это крупные блоки задач, которые включают в себя:

  • setTimeout: Задерживает выполнение части кода до тех пор, пока не пройдёт указанное вами время.
  • setInterval: Непрерывно выполняет часть кода через каждый указанный интервал времени.
  • События и операции ввода-вывода (I/O): Например, нажатие кнопки или загрузка данных из внешнего источника.

Каждая макрозадача выполняется полностью перед переходом к следующей.

Что такое микрозадачи?

Микрозадачи — это мелкие задачи, которые необходимо выполнить без прерывания. К ним относятся:

  • Разрешение промисов (Promise resolutions): Действия, которые вы обещаете выполнить, когда завершится что-то другое.
  • Функция queueMicrotask: Непосредственно добавляет микрозадачи.

Микрозадачи обрабатываются быстрее и чаще, чем макрозадачи. Они выполняются после каждой макрозадачи и до того, как движок JavaScript приступит к следующей макрозадаче.

Примеры кода из реальной практики

Пример 1: Использование setTimeout в качестве макрозадачи

Представьте, что вы хотите вывести сообщение на веб-странице через 2 секунды.


Output appears here after Run.

Пояснение: Этот код настраивает таймер, который ожидает 2 секунды перед выполнением. Затем он изменяет текст элемента на странице (требуется среда браузера). Это макрозадача, поскольку она запланирована на выполнение независимо от основного потока программы.

Пример 2: Использование промисов в качестве микрозадач

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


Output appears here after Run.

Пояснение: Этот код создаёт промис, который разрешается немедленно, а затем сразу же выполняет код внутри .then() после очистки стека. Это микрозадача, поскольку это небольшая задача, которую необходимо выполнить сразу после текущего кода.

Подробнее о приоритете микро- и макрозадач

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

Вот простой пример, демонстрирующий это в действии:


Output appears here after Run.

Ожидаемый вывод:


console
Start
End
Promise 1
Promise 2
Timeout 1
Timeout 2

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

Заключение

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

Практика

В JavaScript, что происходит, когда промис разрешается и к нему прикреплен обработчик `.then()`?

Считаете ли это полезным?

Предпросмотр dual-run — сравните с маршрутами Symfony на продакшене.