← Назад к вопросам

Как работает очередь вызовов в JS?

2.0 Middle🔥 162 комментариев
#JavaScript Core

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает очередь вызовов в JavaScript

JavaScript, несмотря на свою простоту в использовании, обладает асинхронной моделью выполнения, которая базируется на трёх ключевых компонентах: стек вызовов (Call Stack), очередь задач (Task Queue) и цикл событий (Event Loop). Понимание их взаимодействия критично для разработчика, так как оно определяет порядок выполнения операций и обработку асинхронных действий.

Стек вызовов (Call Stack)

Стек — это структура данных, работающая по принципу LIFO (Last In, First Out). Он отвечает за выполнение всех синхронных операций в JavaScript.

function first() {
  console.log('First');
  second();
}
function second() {
  console.log('Second');
}
first();

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

  • При вызове first() он помещается в стек.
  • Внутри first() вызывается second(), который добавляется наверх стека.
  • second() завершается, удаляется из стека.
  • Затем завершается first().

Стек обрабатывает операции последовательно, и если он переполняется (например, бесконечная рекурсия), возникает ошибка "stack overflow".

Очередь задач (Task Queue) и микроочередь (Microtask Queue)

Когда встречаются асинхронные операции (например, setTimeout, fetch, события), они не блокируют стек. Их колбэки помещаются в одну из двух очередей:

  1. Task Queue (макроочередь): Для колбэков от setTimeout, setInterval, событий DOM, I/O операций.
  2. Microtask Queue: Для колбэков от Promise.then/catch/finally, queueMicrotask, process.nextTick в Node.js.

Цикл событий (Event Loop)

Event Loop — это механизм, который непрерывно проверяет состояние стека и очередей. Его алгоритм можно упростить до следующих шагов:

1. Выполнить все синхронные операции в стеке (если он не пуст).
2. Если стек пуст, проверить микроочередь (Microtask Queue).
   - Выполнить все задачи из микроочереди до её опустошения.
3. Затем проверить макроочередь (Task Queue).
   - Если там есть задачи, переместить первую в стек для выполнения.
4. Повторять цикл.

Это объясняет, почему микроташки имеют приоритет над макроташками. Например:

console.log('Start');

setTimeout(() => console.log('Timeout'), 0);

Promise.resolve()
  .then(() => console.log('Promise'));

console.log('End');

Вывод будет:

Start
End
Promise
Timeout

Объяснение:

  • Синхронные console.log выполняются сразу.
  • Колбэк Promise.then попадает в микроочередь и выполняется перед макроташкой (setTimeout), даже если setTimeout имеет нулевую задержку.

Практические следствия для разработчика

  • Неблокирующая асинхронность: Благодаря очереди вызовов, JavaScript может обрабатывать операции без блокировки основного потока (например, сетевые запросы).
  • Рендеринг в браузере: В браузерных средах цикл событий также координируется с рендерингом. После выполнения микроташек браузер может обновить UI перед переходом к макроташкам.
  • Опасности: Если микроочередь постоянно заполняется (например, рекурсивные Promise.then), она может блокировать выполнение макроташек и рендеринга.
  • Порядок выполнения: Знание порядка помогает избегать "гонок данных" и правильно планировать асинхронную логику.
// Пример потенциальной проблемы
function infiniteMicrotasks() {
  Promise.resolve().then(infiniteMicrotasks);
}
infiniteMicrotasks(); // Блокирует выполнение других задач!

Таким образом, очередь вызовов в JS — это не просто линейная очередь, а система с приоритетами (микро vs макро), управляемая циклом событий. Это позволяет JavaScript, хотя и являясь однопоточным языком, эффективно работать с асинхронными операциями, обеспечивая быстрое реагирование и высокую производительность в веб-среде. Для разработчика важно учитывать этот порядок при работе с промисами, таймерами и событиями, чтобы создавать корректные и оптимизированные приложения.