Как проходят таски в event loop
Что проверяют
Понимаете ли вы порядок: одна задача, затем все микрозадачи, затем возможность отрисовки. После этого почти всегда дают кусок кода с setTimeout, Promise и await и просят назвать порядок вывода, а потом спрашивают, почему зависает интерфейс.
Как отвечать
Опишите цикл так, как он задан в спецификации HTML:
- задача (task, «макрозадача»): выполнение скрипта, колбэк
setTimeoutиsetInterval, событие ввода, сообщение черезMessageChannelилиpostMessage. Очередей задач несколько, и браузер сам выбирает, из какой брать, поэтому ввод пользователя может обгонять таймеры; - один оборот: берётся одна задача и выполняется до конца, пока стек вызовов не опустеет;
- микрозадачи: сразу после задачи очередь микрозадач выполняется целиком, включая добавленные по ходу. Сюда попадают реакции промисов (
then,catch, продолжение послеawait),queueMicrotaskиMutationObserver; - отрисовка: затем браузер может обновить кадр. Перед этим вызываются колбэки
requestAnimationFrame, дальше стили, layout и paint. Происходит это не на каждом обороте, а примерно с частотой экрана; - следующая задача, и всё сначала.
Отсюда классический порядок: синхронные console.log, затем промис, затем setTimeout(..., 0). Нулевой задержки у таймера к тому же не бывает: при глубокой вложенности браузер поднимает её до 4 мс, а в фоновой вкладке таймеры сильно замедляются.
Практический вывод: долгая задача (больше 50 мс, так её считает Long Tasks API) блокирует и ввод, и отрисовку. Её дробят на куски и отдают управление через setTimeout или scheduler.yield() в Chromium, а тяжёлые вычисления уносят в Web Worker.
Где спотыкаются
Говорят, что микрозадачи выполняются по одной между задачами. Очередь выполняется до опустошения, и рекурсивный then повесит вкладку так же, как бесконечный цикл.
Считают requestAnimationFrame микрозадачей или обычным таймером. Это отдельный шаг перед отрисовкой кадра.
Путают с Node.js: фазы libuv и process.nextTick к браузерному циклу не относятся.