В чем главное условие переноса задач из микротасков в макротаски
Что проверяют
Формулировка путаная, но смысл один: знаете ли вы, когда event loop переходит от микрозадач к следующей макрозадаче. Правильный ответ: только когда стек вызовов пуст и очередь микрозадач опустела полностью, включая микрозадачи, добавленные по ходу. Следом часто дают код с setTimeout, Promise.then и queueMicrotask и просят назвать порядок вывода.
Как отвечать
Один оборот цикла в браузере:
- макрозадача: из очереди берётся одна задача, например колбэк
setTimeout, обработчик события, сообщениеpostMessage; - микрозадачи: после неё выполняются все микрозадачи до последней: колбэки
then,catch,finally, продолжение послеawait,queueMicrotask,MutationObserver. Если микрозадача ставит новую, та выполняется в этом же проходе; - рендер: затем браузер может обновить экран, перед отрисовкой вызываются колбэки
requestAnimationFrame; - следующая макрозадача.
Отсюда и условие перехода: очередь микрозадач должна быть пуста. Бесконечная цепочка промисов не пропустит ни следующий setTimeout, ни отрисовку, вкладка зависнет так же, как от while (true).
Если вопрос в практическом смысле, когда переносить работу из микрозадачи в макрозадачу, ответ такой: когда нужно отдать управление браузеру. Длинную обработку данных режут на куски и планируют через setTimeout или MessageChannel, в Chromium есть scheduler.postTask() и scheduler.yield(). Тогда между кусками успеют обработаться ввод пользователя и рендер, и не пострадает INP.
В Node.js схема похожая, но с фазами libuv, а очередь process.nextTick обычно обрабатывается раньше промисов.
Где спотыкаются
Считают, что микрозадачи чередуются с макрозадачами по одной. На деле очередь микрозадач выгребается целиком, и только потом берётся следующая макрозадача.
Думают, что setTimeout(fn, 0) выполнится сразу. Он встанет в очередь макрозадач, а при глубокой вложенности таймеров браузер ещё и поднимает задержку до 4 мс.
Путают, что здесь синхронно: тело new Promise(executor) выполняется сразу, асинхронны только колбэки then, а код после await уходит в микрозадачу.