> Куда пойдет выполнение после запуска промиса в JavaScript (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Black Wall Group (BWG)

Стек: JavaScript

> Пример ответа

Короткий ответ

После запуска промиса выполнение не блокируется: код продолжает синхронно выполняться дальше, а колбэк .then()/.catch()/.finally() попадает в microtask queue и будет вызван после завершения текущего макрозадачи (macrotask) и очистки стека вызовов. Сам промис переходит в состояние pending, а затем, после разрешения, его обработчики ставятся в очередь микрозадач.

Подробное объяснение

Промис - это объект, представляющий асинхронную операцию. При создании new Promise(executor) функция executor выполняется синхронно - это важно. Внутри executor вызывается resolve() или reject(), что переводит промис в соответствующее состояние (fulfilled/rejected). Однако обработчики .then(), .catch() и .finally(), привязанные к этому промису, не вызываются немедленно. Они помещаются в microtask queue (очередь микрозадач) и будут выполнены только после того, как текущий синхронный код завершится и стек вызовов опустеет.

Механизм event loop работает так:

  1. Выполняется текущая макрозадача (macrotask) - например, скрипт, обработчик клика, setTimeout.
  2. После её завершения проверяется очередь микрозадач. Все накопившиеся микрозадачи выполняются по очереди, пока очередь не опустеет.
  3. Затем браузер может обновить рендеринг (если нужно).
  4. Извлекается следующая макрозадача.

Таким образом, после запуска промиса (вызова resolve() внутри executor) код продолжает выполняться синхронно, а .then() сработает только после завершения всего синхронного кода в текущей макрозадаче.

На практике

  • Если внутри промиса есть resolve() в конце синхронного кода, обработчик .then() всё равно выполнится асинхронно, после текущего стека.
  • Если промис создаётся, но его executor содержит setTimeout, то resolve() будет вызван позже, и .then() сработает после таймера.
  • Важно не путать microtask (промисы, MutationObserver) с macrotask (setTimeout, setInterval, I/O). Промисы имеют приоритет перед таймерами.
  • При цепочке .then() каждый следующий обработчик тоже становится микрозадачей и выполняется после предыдущего, но до следующей макрозадачи.

Пример кода

JAVASCRIPT
console.log('1: синхронный код');
const promise = new Promise((resolve) => {
console.log('2: executor выполняется синхронно');
resolve('3: промис разрешён');
});
promise.then((value) => {
console.log(value); // выведется после '4'
});
console.log('4: ещё синхронный код');
// Вывод:
// 1: синхронный код
// 2: executor выполняется синхронно
// 4: ещё синхронный код
// 3: промис разрешён

Как отвечать на собеседовании

Начни с ключевого факта: executor промиса выполняется синхронно, а .then() - асинхронно, через microtask queue. Объясни порядок event loop: сначала стек, потом microtask, потом macrotask. Приведи простой пример с выводом в консоль, чтобы показать разницу. Упомяни, что это поведение стандартизировано в спецификации ECMAScript (Job и PromiseReactionJob). Если собеседник спросит про отличие от setTimeout(fn, 0), объясни, что microtask имеют приоритет перед macrotask.

Что проверяет интервьюер

  • Понимание event loop и очередей (microtask vs macrotask).
  • Знание, что executor промиса синхронен.
  • Умение предсказать порядок выполнения кода с промисами.
  • Осознание разницы между асинхронностью промиса и таймеров.
  • Глубина: знание спецификации (PromiseJobs, HostEnqueuePromiseJob).

Типичные ошибки

  • Утверждение, что .then() выполняется сразу после resolve() - это неверно, он всегда асинхронен.
  • Путаница между microtask и macrotask: думают, что .then() и setTimeout равнозначны по приоритету.
  • Игнорирование синхронности executor: считают, что весь код внутри промиса асинхронен.
  • Непонимание, что вложенные промисы (chain) создают новые микрозадачи, а не выполняются в одном тике.

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью