awesome-everything EN
↑ Обратно к восхождению

Браузер и фронтенд-рантайм

Event loop: один поток, три очереди

Суть Как единственный поток браузера чередует задачи, микрозадачи и рендеринг — и почему тот, кто работает дольше всех, блокирует всех.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на junior-высоте — поверхность
◷ 12 min

Клик по кнопке ощущается мгновенным. Скролл дёргается. Модалка замирает на 300 мс, пока парсится JSON. То, что решает, какой опыт получит пользователь, — это один механизм старше современного веба: event loop.

Что делает event loop

У браузера один поток, исполняющий ваш JavaScript. Пока он занят, ничего другого не запускается — ни кликов, ни анимаций, ни рендеринга. Event loop решает, что запустить следующим, когда поток освобождается.

В цикле три игрока:

  • Очередь задач (task queue, макрозадачная очередь) — дискретные работы: колбэки setTimeout, распарсенные куски HTML, сработавшие DOM-события, сообщения message-channel.
  • Очередь микрозадач (microtask queue) — колбэки резолва промисов и работы queueMicrotask. Дренируется до пустой после каждой задачи.
  • Шаги рендеринга — стили, компоновка, отрисовка, композитинг. Идут между итерациями, но только когда браузер считает, что кадр пора показать.
Бюджеты и лимиты event loop
Бюджет кадра при 60 fps
16,67 мс
Порог длинной задачи
50 мс
Хороший INP (p75)
≤200 мс
Зажим setTimeout(0) после 5 уровней
4 мс
Срез работы React 18
5 мс
Cross-thread postMessage hop
~1 мс + клон

Метафора бариста

Представьте кофейню с одним сотрудником. Клиенты кладут заказы на стойку (очередь задач). Закончив один напиток, бариста сразу проверяет стикеры на эспрессо-машине (микрозадачи — мелкие продолжения вроде «добавить корицу») и делает все стикеры до приветствия следующего клиента. Если она пишет себе стикеры быстрее, чем успевает их выполнять, она вообще не доходит до следующего клиента. Этот режим отказа — голодание микрозадачами (microtask starvation).

Каждая анимация, каждый напечатанный символ, каждый клик, каждый скролл — это задача в очереди, ждущая поток. Держите поток 200 мс — пользователь видит 200 мс замороженного UI. Лечение — не «более быстрый код», а «более короткие задачи».

Одна итерация, шаг за шагом

Одна итерация цикла делает ровно следующее:

ШагЧто выполняетсяКогда пропускается
1. Выбор задачиВытянуть одну задачу из непустой очередиЕсли все очереди пусты — простой
2. Выполнение задачиИсполнить колбэк до концаНикогда, если шаг 1 выполнился
3. Microtask checkpointДренировать очередь микрозадачОчередь микрозадач пуста
4. Обновление рендерингаrAF → стили → компоновка → отрисовка → композитингТолько на границе кадра (~16,67 мс)
5. В началоВернуться к шагу 1Никогда

Конкретный сценарий

Антон · Браузер жмёт Submit. Дима · Origin-сервер комментирует: «Событие клика встаёт в очередь как задача. Запускается submit-обработчик — 4 мс на валидацию, ставит fetch. Fetch резолвится позже, планирует микрозадачу .then(). Микрозадача выполняется, меняет состояние. React делает re-render — ещё JS, ещё работа на том же потоке. Итого: 28 мс. Бюджет кадра — 16,67 мс, значит пользователь видит один пропущенный кадр.»

Расставь шаги по порядку

Внутри одной итерации event loop браузера эти шаги идут в фиксированном порядке. Расставьте их в правильную последовательность.

  1. 1 Вытянуть одну задачу из очереди задач и выполнить до конца
  2. 2 Дренировать очередь микрозадач (выполнять колбэки промисов до пустой)
  3. 3 Запустить колбэки requestAnimationFrame (только если кадр пора показать)
  4. 4 Стили → компоновка → отрисовка → композитинг (только если кадр пора)
  5. 5 Вернуться в начало: вытянуть следующую задачу
Викторина

Внутри обработчика `setTimeout` вы вызываете `Promise.resolve().then(work)`. Когда выполнится `work`?

Викторина

Click-обработчик работает 400 мс. Что из этого продолжает работать во время блокировки?

Закончи аналогию

В метафоре бариста заказы клиентов лежат на стойке — это очередь задач. Стикеры самой бариста от предыдущего заказа, которые она доделывает до приветствия следующего клиента, — это какой вид очереди?

Посчитай

Задача длится дольше скольки миллисекунд, и браузер официально называет её «длинной задачей» (long task), вносящей вклад в плохой INP?

мс
Вспомните перед уходом
  1. 01
    Назовите трёх игроков event loop браузера.
  2. 02
    Почему click-обработчик на 400 мс замораживает страницу, хотя CSS-анимации продолжают работать?
  3. 03
    Какой порог у W3C для «длинной задачи» и почему это важно для INP?
Итог

Event loop браузера — однопоточный движок, обрабатывающий одну задачу за раз — колбэк setTimeout, click-обработчик, распарсенный кусок HTML. После каждой задачи он полностью дренирует очередь микрозадач (резолвы промисов, колбэки MutationObserver), прежде чем рендерить или обрабатывать ввод. Это делает очередь микрозадач продолжением выполняющейся задачи с точки зрения рендерера. Задачи длиннее 50 мс — длинные задачи; они откладывают рендеринг и обработку ввода, напрямую раздувая INP. Бюджет кадра при 60 fps — 16,67 мс, поэтому 200 мс задача роняет примерно 12 кадров и пользователь ощущает очевидный jank.

Связанные уроки
встречается в143
Продолжить восхождение ↑Задачи, микрозадачи и scheduler.yield()
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.