Что происходит при встрече промиса во время выполнения синхронного кода в JavaScript
Что проверяют
Различаете ли вы момент создания промиса и момент выполнения его обработчиков. Ошибка тут рождает код, который «иногда работает»: данные читают раньше, чем они пришли, а потом списывают это на гонки и чинят таймерами.
Как отвечать
Встретив создание промиса, движок выполняет переданную функцию немедленно и синхронно, прямо в текущем стеке. Асинхронно в ней только то, что вы сами туда положили: таймер, сетевой запрос, подписка на событие. Сам промис при этом лишь объект с состоянием ожидания и списком подписчиков.
Вызов .then тоже синхронный, он только регистрирует колбэк. Сам колбэк не выполняется сразу никогда, даже если промис уже выполнен: он ставится в очередь микрозадач и запускается, когда синхронный код закончился и стек опустел, но раньше ближайшей макрозадачи и раньше отрисовки.
console.log(1);
new Promise((resolve) => {
console.log(2);
resolve();
}).then(() => console.log(4));
console.log(3);
// 1, 2, 3, 4С await то же самое другими словами: тело асинхронной функции выполняется синхронно до первого ожидания, а всё, что после, становится продолжением в очереди микрозадач. Поэтому await не блокирует поток, он возвращает управление наружу.
Где спотыкаются
Считают, что содержимое промиса выполняется где-то в фоне. Функция-исполнитель обычная и синхронная: тяжёлый цикл внутри неё повесит вкладку ровно так же, как вне промиса.
Читают результат строкой ниже. Присвоение внутри .then случится позже, чем следующая строка снаружи, поэтому в переменной окажется undefined. Продолжение обязано быть внутри обработчика или после ожидания, а не через проверку по таймеру.
Думают, что уже выполненный промис зовёт обработчик синхронно. Готовый результат всё равно откладывает колбэк в микрозадачи, и ожидание обычного значения, а не промиса, тоже даёт отсрочку на один шаг.