У микротасок отдельная очередь выполнения. Если очень примитивно представить event-loop браузера, то получится код вроде такого:
// весь event-loop
while (true) {
// цикл микротасок:
while (!microtaskQueue.isEmpty) {
microtaskQueue.runNext();
}
if (!macrotaskQueue.isEmpty) {
macrotaskQueue.runNext()
}
}
Как видно из этого псевдокода, очередь микротасков будет выполнятся пока она не закончится, а очередь макротасков будет выполнять по 1 таске на итерацию event-loop. Соответственно, если в микротаске добавить в очередь еще одну микротаску, то она выполнится на той же итерации event-loop до того как управление перейдет к макротаскам.
Этим кстати объясняется то, что такая функция вешает вкладку браузера намертво:
function microtaskBomb() {
return Promise.resolve().then(microtaskBomb);
}
И да, в моем примере очень примитивное представление event-loop. На самом деле там происходит гораздо больше интересных вещей. Например requestAnimationFrame живут в своей очереди, которая тоже обрабатывается по особенному.