Разберись, как движок на самом деле планирует синхронный код, микрозадачи и макрозадачи.
Открыть этот урок в KodokonJavaScript выполняет твой код в одном потоке, но цикл событий не описан в ECMAScript: то, как он работает, задаёт спецификация HTML (и libuv на стороне Node.js). Движок держит стек вызовов для кода, который выполняется прямо сейчас, очередь макрозадач (таймеры, ввод-вывод, события) и очередь микрозадач (Promise.prototype.then, queueMicrotask, MutationObserver). Золотое правило: после каждой макрозадачи очередь микрозадач опустошается полностью, и только потом управление возвращается наружу.
console.log("script start");
setTimeout(() => console.log("timeout"), 0);
Promise.resolve()
.then(() => console.log("promise 1"))
.then(() => console.log("promise 2"));
console.log("script end");
// script start, script end,
// promise 1, promise 2, timeoutПервоначальный скрипт сам по себе является макрозадачей: пока он не закончится, ничего другого не выполняется. Как только стек вызовов пустеет, движок опустошает очередь микрозадач, включая микрозадачи, добавленные во время опустошения. Эта деталь объясняет, почему два сцепленных вызова then выполняются раньше setTimeout(fn, 0): каждый then планирует новую микрозадачу, а управление не возвращается наружу, пока очередь не опустеет.
setTimeout(() => console.log("macro 1"), 0);
setTimeout(() => console.log("macro 2"), 0);
queueMicrotask(() => {
console.log("micro 1");
queueMicrotask(() => console.log("micro 2"));
});
// micro 1, micro 2, macro 1, macro 2Ключевое слово await опирается на этот механизм: оно приостанавливает асинхронную функцию, возвращает управление вызывающему коду, а затем планирует возобновление как микрозадачу. Даже await null вызывает эту приостановку, потому что значение оборачивается в уже разрешённый промис. Именно поэтому код, стоящий после await, никогда не выполняется синхронно.
async function main() {
console.log("before await");
await null;
console.log("after await");
}
main();
console.log("sync code");
// before await, sync code, after awaitC, планирует A через setTimeout(fn, 0) и B через Promise.resolve().then. В каком порядке появятся буквы?await под капотом?