Что происходит после выполнения микротасков в JavaScript и как обрабатываются макротаски и синхронный код?
Что проверяют
Знаете ли вы порядок точнее, чем «промисы раньше таймеров». Вопрос как раз про то, что идёт после разгребания микрозадач, и полный ответ включает отрисовку, про которую обычно забывают.
Как отвечать
Цикл выглядит так: берётся одна макрозадача и выполняется до конца, затем полностью разгребается очередь микрозадач, и только потом браузер может отрисовать кадр. После кадра берётся следующая макрозадача.
Что куда попадает:
- синхронный код это тело текущей задачи, оно идёт целиком, ничего в середину не вклинивается;
- микрозадачи это колбэки промисов, продолжение функции после
await,queueMicrotask,MutationObserver; - макрозадачи это
setTimeoutиsetInterval, обработчики событий, сетевые колбэки, сообщения; requestAnimationFrameне относится ни к тем, ни к другим: его колбэки вызываются перед отрисовкой кадра, поэтому анимации делают через него, а не таймером.
Важная деталь: микрозадачи, добавленные во время разгребания очереди, выполняются в том же заходе, а не переносятся на следующий. Отсюда и практический вывод: длинная цепочка промисов задерживает кадр ровно так же, как долгий синхронный код.
Если позиция подразумевает Node.js, скажите, что там свои фазы цикла, есть setImmediate, а process.nextTick выполняется раньше промисов.
Где спотыкаются
Думают, что между микрозадачами браузер успевает рисовать. Он не успевает: пока очередь не опустела, кадра не будет, и интерфейс с бесконечно порождающей себя цепочкой промисов виснет так же, как на бесконечном цикле.
Второе: считают setTimeout с нулём мгновенным. После нескольких вложенных таймеров браузер поднимает минимальную задержку, а в фоновой вкладке таймеры замедляются сильно. Строить порядок выполнения на нулевой задержке нельзя.
Третье: думают, что await останавливает всё. Он приостанавливает только свою функцию, вызывающий код продолжает работать, а продолжение после await попадает в микрозадачи и потому идёт раньше любого таймера.