Как JavaScript выполняет асинхронный код: подробный разбор Event Loop, очередей задач и Web APIs

freeCodeCamp.org 9,2 тыс. 46 мин 4 мин 05.05.2026
Главное

Понимание цикла событий (Event Loop) — это водораздел, отделяющий новичка от крепкого специалиста. В новом обучающем курсе от freeCodeCamp.org разработчик и автор контента Висвас (Viswas) детально разбирает внутреннюю кухню JavaScript, объясняя, как язык умудряется выполнять асинхронные задачи, оставаясь при этом строго однопоточным.

🧱 Архитектура JavaScript: за пределами движка 0:00

Многие разработчики ежедневно используют промисы, таймеры и API-запросы, не до конца понимая, как эти инструменты работают «под капотом» . По словам Висваса, Event Loop — это лишь небольшая, но критически важная часть более крупной системы.

Чтобы понять, как работает асинхронность, необходимо различать пять основных компонентов среды исполнения (runtime) :

Висвас подчеркивает, что JavaScript является однопоточным . Это означает наличие только одного стека вызовов, который управляет порядком выполнения программы. Каждая функция помещается в стек (push), выполняется и затем удаляется из него (pop) . Эта система идеально работает для синхронного кода, но становится беспомощной, когда нужно подождать ответа от сервера или срабатывания таймера.

⚡ Web APIs: суперспособности браузера 6:50

Стек вызовов не умеет ставить задачи «на паузу» или задерживать их выполнение. Если бы он пытался это делать, приложение бы просто зависло (заблокировалось) . Для решения этой проблемы JavaScript полагается на «суперсилы», находящиеся вне самого языка — Web APIs .

Web APIs — это функциональность, предоставляемая браузером (или средой исполнения), а не самим ядром JavaScript . К наиболее важным из них относятся:

Висвас обращает внимание на распространенное заблуждение: многие считают эти API частью языка JavaScript, хотя на самом деле они доступны через глобальный объект window только благодаря среде браузера .

🔄 Как работает связка Event Loop и Task Queue 10:41

Для иллюстрации процесса Висвас приводит пример с setTimeout и задержкой в 4 секунды . Когда интерпретатор доходит до таймера, управление передается в Web API. Движок JavaScript не ждет завершения отсчета, а сразу переходит к следующей строке кода .

Алгоритм работы в этот момент выглядит так:

  1. Таймер отсчитывает время в фоновом режиме в среде Web API .
  2. Как только время истекает, колбэк-функция (callback) попадает в Task Queue .
  3. Event Loop начинает свою работу: он непрерывно проверяет, пуст ли Call Stack .
  4. Если стек пуст, Event Loop берет первую задачу из Task Queue и перекидывает её в Call Stack для выполнения .

Ключевое правило: Event Loop никогда не добавит задачу в стек, пока тот не будет полностью очищен от текущего синхронного кода .

🔝 Microtask Queue: приоритетная полоса для промисов 27:45

Ситуация усложняется, когда в игру вступают промисы и Fetch API. Висвас объясняет, что задачи от промисов обрабатываются иначе, чем обычные колбэки от таймеров . Для них существует Microtask Queue (очередь микрозадач).

Основное отличие заключается в приоритете:

Висвас приводит пример сценария, где одновременно завершаются сетевой запрос (промис) и таймер. Даже если таймер «созрел» раньше, Event Loop первым делом выполнит колбэк промиса, так как тот находится в приоритетной очереди микрозадач .

В список микрозадач попадают :

  1. Колбэки промисов (.then(), .catch(), .finally()).
  2. Mutation Observer (отслеживание изменений в DOM).
  3. Код внутри async/await после ключевого слова await.
  4. Функции, добавленные через queueMicrotask().

⚠️ Проблема «голодания» (Starvation) 41:05

Одной из самых коварных тем на интервью Висвас называет «голодание» функций (Starvation) . Поскольку Event Loop отдает абсолютный приоритет очереди микрозадач, может возникнуть ситуация, когда обычные задачи из Task Queue никогда не будут выполнены.

Это происходит, если одна микрозадача порождает другую микрозадачу, создавая бесконечный цикл . В этом случае стек вызовов будет постоянно занят микрозадачами, а колбэки от таймеров или кликов пользователя будут «голодать», ожидая своей очереди в Task Queue бесконечно долго .

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

💬 Цитаты

«Если вы понимаете асинхронный JavaScript и цикл событий в деталях, вы сможете пройти почти любое фронтенд-интервью.»

«Event Loop никогда не добавляет никаких задач в стек вызовов, пока этот стек не станет полностью пустым.»

«Микрозадачи всегда имеют более высокий приоритет, чем обычная очередь колбэков.»

👥 Спикер
🔗 Упомянутые сайты и проекты
📖 Термины
Call Stack
Механизм для отслеживания порядка выполнения функций в JavaScript; работает по принципу LIFO (последним пришел — первым вышел).
Microtask Queue
Специальная очередь для высокоприоритетных задач, таких как промисы, которая обрабатывается сразу после завершения текущего синхронного кода.
Event Loop
Бесконечный цикл, который проверяет, пуст ли Call Stack, и переносит задачи из очередей в стек выполнения.
Starvation
Состояние, при котором задачи из основной очереди не могут выполниться из-за непрерывного потока приоритетных микрозадач.
📊 Цифры
🗓 Хронология
  1. 00:00 Начало разбора структуры JavaScript Engine и окружения браузера.
  2. 10:41 Разбор первого примера с использованием таймера и Task Queue.
  3. 27:45 Объяснение работы Fetch API и введение понятия Microtask Queue.
  4. 41:05 Обсуждение концепции Starvation (голодания функций).
⚖️ Другая сторона
Технологии и IT JavaScript Event Loop Web API Microtask Queue Asynchronous Programming