Тонкости исполнения кода с микротасками
Я пытаюсь разобраться с последовательностью исполнения кода, когда в нём присутствуют микротаски. Я знаю, что в теории микротаски пушатся в очередь, когда стек вызовов пустой (т.е. когда на нём лежит только глобальный контекст). Например, у нас в глобальном контексте есть два последовательных for-цикла:
for(let i = 0; i<1000; i++){
console.log(i);
};
for(let i = 0; i<1000; i++){
console.log(i);
};
Правда ли, что до, после и во время исполнения кода циклов условие пустого стека выполняется? Если так, то почему микротаск (res)=> { console.log(res)}
в коде ниже не выполняется между двумя последними циклами, если к тому времени он уже в microtask-очереди и стек вызовов пустой?
const promise = new Promise(function(resolve, _){
resolve('microtask2');
});
for(let i = 0; i<1000; i++){
console.log(i);
};
promise.then((res)=> {
console.log(res);
)};
for(let i = 0; i<1000; i++){
console.log(i);
};
for(let i = 0; i<1000; i++){
console.log(i);
};
Вот примерно так я вижу порядок исполнения кода:
- Eexecutor-функция, передаваемая внутрь конструктора
Promise()
, вызывает методresolve()
и немедленно исполняет этот Promise. - Выполняется первый цикл.
- Вызывается
then()
, его контекст помещается в стек вызовов (поверх глобального контекста). - Колбэк внутри
then()
регистрируется где-то в среде браузера. - Контекст
then()
убирается из стека. - Выполняется еще один цикл, так как колбэк из
then()
только лишь зарегистрирован, и еще не находится в очереди (или он сразу попадает в очередь?). - Теперь, когда цикл завершен и стек вызовов пуст, event loop берёт колбэк из очереди, перебрасывает его в стек вызовов и выполняет (я знаю, что это не так, и колбэк будет исполнен последним, но я не понимаю почему).
- Последний цикл выполняется К шагу 7 уже выполняются все условия для вызова колбэка:
а) в стеке ничего нет б) синхронный код в данный момент не выполняется в) колбэк ожидает в очереди
Почему колбэк находится в очереди до тех пор, пока не будет выполнен последний цикл, если между циклами есть множество "окошек" для выполнения?
Ответ на комментарий Alexey Ten:
Я сначала так и думал, что а первую очередь выполняется весь синхронный код, а уже затем проверяются очереди, но потом поигрался в IDE и получил код, где колбэк исполняются до того, как исполнится весь синхронный код:
const timer = setTimeout(function(){
console.log('set timeout');
}, 0);
new Promise(function(resolve, _){
try{
resolve('microtask1');
}
catch(err){
console.error(`caught an ${err}`);
}
}).then((res)=>{
console.log(res);
});
console.log('synchronous code 1');
const result = await fetch(`https://jsonplaceholder.typicode.com/photos`);
console.log('synchronous code 2');
Если вы этот код запустите, то будет вот такой вывод:
Т.е. строка синхронного кода запустилась после колбэков.
Возможно, дело в том, что выражение с await
здесь использовано вне функции, как это было всегда, а на уровне глобального скоупа, как это стало возможным с ES2022 и есть какие-то особенности, которые я не учёл? На эту мысль меня наводит тот факт, что если await
поместить в асинхронную функцию, то проблема исчезает и всё выводится на экран, в том порядке, в котором и ожидается:
let result;
const timer = setTimeout(function(){
console.log('set timeout');
}, 0);
new Promise(function(resolve, _){
try{
resolve('microtask1');
}
catch(err){
console.error(`caught an ${err}`);
}
}).then((res)=>{
console.log(res);
});
console.log('synchronous code 1');
(async function(){
result = await fetch(`https://jsonplaceholder.typicode.com/photos`);
})();
console.log('synchronous code 2');
Вывод на экран:
Как думаете, связано ли это как-то со спецификой await
?