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

Какие есть очереди в Event Loop кроме micro и macro tasks?

2.2 Middle🔥 281 комментариев
#JavaScript Core

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

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

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

Отличный вопрос, который затрагивает самую суть асинхронного выполнения в JavaScript. Вы правы, классификация на microtask и macrotask (чаще называемые просто tasks) — это фундаментальная модель. Однако, чтобы дать полный ответ, нужно разобрать устройство Event Loop глубже и посмотреть, как в эту модель встраиваются другие механизмы.

Если коротко: формально в спецификации Event Loop есть только задачи (tasks) и микро-задачи (microtasks). Но на практике, для понимания порядка выполнения, мы часто мысленно выделяем дополнительные "очереди" или, точнее, источники задач (task sources) с разными приоритетами и поведением. Вот как это выглядит в деталях.

📚 Базовая модель: Task и Microtask

Для начала освежим основы. Спецификация WHATWG описывает Event Loop так:

  • Task (Macrotask): Крупная единица работы. Каждый цикл Event Loop выбирает и выполняет ровно одну задачу из очереди задач. К ним относятся:
    *   `setTimeout` и `setInterval`
    *   События DOM (`click`, `keypress`, `load`)
    *   `requestAnimationFrame` (хотя его поведение особое, об этом ниже)
    *   Сетевые события, `fetch` (ответ попадает в задачу)
    *   Ввод-вывод (I/O)
    *   `postMessage`
    *   Код в `<script>` (выполнение основного скрипта)

  • Microtask: Мелкая единица работы, выполняемая сразу после текущей задачи, до рендеринга и до выбора следующей задачи. Очередь микро-задач должна быть полностью опустошена. К ним относятся:
    *   `Promise.then/catch/finally`
    *   `queueMicrotask()`
    *   `MutationObserver` (для наблюдения за изменениями DOM)

Классический пример порядка:

console.log('Script start'); // Task

setTimeout(() => console.log('setTimeout'), 0); // Task -> в очередь задач

Promise.resolve()
    .then(() => console.log('Promise 1')) // Microtask -> в очередь микро-задач
    .then(() => console.log('Promise 2')); // Microtask -> в очередь микро-задач

console.log('Script end'); // Task

// Порядок вывода:
// Script start
// Script end
// Promise 1
// Promise 2
// setTimeout

🎯 "Скрытые" очереди и источники задач

Теперь к сути вашего вопроса. Помимо явного разделения на task/microtask, в браузерах существуют внутренние механизмы планирования, которые можно условно считать отдельными "очередями" с точки зрения приоритета.

1. Очередь отложенных колбэков requestAnimationFrame (rAF)

requestAnimationFrame — это не макро- и не микро-and задача в чистом виде. Это хук на фазу рендеринга в цикле событий.

  • Его колбэки выполняются перед репайнтом и рекомпозицией браузера, в специальном шаге "Update the rendering".
  • Они имеют более высокий приоритет, чем задачи от setTimeout, запланированные с аналогичной задержкой, потому что браузер старается синхронизировать их с кадром анимации (обычно 60 FPS).
  • Важно: Если внутри колбэка rAF сгенерируется микро-задача (например, Promise), она выполнится сразу, как и положено, до следующего этапа цикла.
setTimeout(() => console.log('Timeout'), 0); // Task
requestAnimationFrame(() => console.log('rAF')); // Callback в шаге рендеринга

// Порядок может быть разным, но чаще при нулевой задержке:
// rAF (т.к. вызывается перед рендерингом в текущем цикле)
// Timeout (в следующем цикле Event Loop)

2. Очередь сообщений postMessage (MessageChannel)

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

3. Очередь событий ввода (Input Events)

События от мыши, клавиатуры, касаний (особенно pointermove, scroll) часто планируются в отдельные, высокоприоритетные очереди. Браузер старается обработать их как можно быстрее для обеспечения отзывчивого интерфейса. Они являются задачами (tasks), но могут "обгонять" в очереди, например, колбэки от setTimeout.

4. Очередь requestIdleCallback

Это полная противоположность rAF. Его колбэки планируются на выполнение в периоды простоя браузера, когда основная очередь задач пуста и нет срочных задач рендеринга или ввода. Это самая низкоприоритетная "очередь".

requestIdleCallback((deadline) => {
    console.log('Idle callback. Time remaining:', deadline.timeRemaining());
    // Здесь выполняем не критичные фоновые задачи
});

🎨 Приоритеты в современном Event Loop (браузер)

Если обобщить и расположить условные "очереди" по приоритету выполнения в одном цикле или между циклами, картина будет такой:

  1. Текущая выполняющаяся задача (например, синхронный код).
  2. Вся очередь микро–задач (выполняется до очистки).
  3. Шаг рендеринга (если требуется):
    *   Вызов колбэков `requestAnimationFrame`.
    *   Расчет стилей, layout, paint.
  1. Задачи (Tasks) — здесь уже работает внутренний планировщик браузера, который может отдавать приоритет:
    *   **Критические задачи:** События ввода (`click`, `pointerdown`).
    *   **Задачи передачи данных:** `postMessage`, завершенные `fetch`.
    *   **Отложенные задачи:** `setTimeout`, `setInterval`.
    *   **Задачи простоя:** `requestIdleCallback` (выполняются в последнюю очередь).

💎 Вывод

Так что, формально спецификация говорит только о задачах (tasks) и микро-задачах (microtasks). Но для глубокого понимания производительности и предсказания порядка выполнения в реальном браузере необходимо учитывать, что:

  • requestAnimationFrame — это не задача, а хук на рендеринг.
  • requestIdleCallback — это задача самого низкого приоритета.
  • Браузерные движки (Blink, Gecko, WebKit) используют внутренний планировщик, который разбивает общую очередь задач на несколько приоритетных подочередей (для событий, таймеров, сетевых операций), чтобы интерфейс оставался плавным и отзывчивым.

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