> Как работает конструктор Promise, синхронность создания и асинхронность резолва в JavaScript (JavaScript)

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

Компании: amoCRM, GoInvest, Яндекс

Стек: JavaScript

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

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

Конструктор Promise принимает executor-функцию, которая выполняется синхронно в момент создания промиса. Внутри executor вызываются resolve/reject - они лишь переводят промис в соответствующее состояние, но не выполняют коллбэки .then/.catch. Асинхронность резолва обеспечивается тем, что коллбэки попадают в microtask queue и выполняются после завершения текущего синхронного кода, но до макрозадач (setTimeout, I/O).

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

Конструктор new Promise(executor) вызывает executor немедленно и синхронно - это часть спецификации ECMAScript (раздел 25.6.3.1). Внутри executor может быть любой код: синхронный (например, вызов resolve("ok")) или асинхронный (setTimeout, fetch, event listener).

Когда resolve или reject вызываются синхронно внутри executor, промис переходит в состояние fulfilled/rejected в том же синхронном потоке. Однако коллбэки, зарегистрированные через .then(), .catch(), .finally(), не вызываются сразу - они ставятся в очередь microtask. Это гарантирует, что код после создания промиса выполнится до обработки результата.

Если executor содержит асинхронную операцию (например, setTimeout с resolve внутри), то resolve вызовется позже, в момент срабатывания таймера. Тогда промис перейдёт в нужное состояние, и коллбэки попадут в microtask queue.

Важно: resolve/reject могут быть вызваны только один раз - последующие вызовы игнорируются. Состояние промиса фиксируется при первом вызове.

На практике

Понимание синхронности executor критично для отладки и предотвращения race conditions. Например, если внутри executor сразу вызывается resolve, то .then() всё равно выполнится асинхронно - это позволяет избежать проблем с порядком выполнения, когда промис создаётся до регистрации обработчиков.

Распространённый паттерн - оборачивание callback-based API в промис:

JAVASCRIPT
function readFilePromise(path) {
return new Promise((resolve, reject) => {
fs.readFile(path, (err, data) => {
if (err) reject(err);
else resolve(data);
});
});
}

Здесь executor синхронно запускает fs.readFile, но resolve/reject вызываются асинхронно, внутри коллбэка.

Другой пример - создание уже зарезолвленного промиса через Promise.resolve(). Это удобно для нормализации значений: Promise.resolve(5) создаёт промис, который сразу переходит в fulfilled, но .then() всё равно выполнится в microtask.

Пример кода

JAVASCRIPT
console.log('1: start');
const promise = new Promise((resolve) => {
console.log('2: executor start');
resolve('3: resolved');
console.log('4: executor end');
});
promise.then((value) => {
console.log('5:', value);
});
console.log('6: after promise creation');
// Вывод:
// 1: start
// 2: executor start
// 4: executor end
// 6: after promise creation
// 5: 3: resolved

Обратите внимание: executor выполняется полностью синхронно (2, 4), затем синхронный код продолжается (6), и только потом выполняется коллбэк .then (5) - это и есть асинхронность резолва через microtask.

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

Начни с ключевого факта: executor выполняется синхронно. Приведи пример с порядком вывода, чтобы наглядно показать разницу между синхронным созданием и асинхронным выполнением коллбэков. Упомяни, что resolve/reject - это не вызов .then, а лишь изменение состояния промиса. Объясни роль microtask queue и отличие от макрозадач. Если спросят про Promise.resolve(), отметь, что он тоже создаёт промис, но коллбэки всё равно идут в microtask.

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

  • Понимание event loop и очередей (microtask vs macrotask)
  • Знание спецификации Promise: синхронность executor, однократность resolve/reject
  • Умение предсказывать порядок выполнения кода с промисами
  • Навыки работы с асинхронными паттернами (промисфикация callback'ов)

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

  • Утверждение, что resolve/reject вызывают .then синхронно
  • Путаница между синхронным выполнением executor и асинхронным вызовом коллбэков
  • Игнорирование того, что код после resolve внутри executor всё равно выполняется
  • Предположение, что Promise.resolve() выполняет .then немедленно
  • Забывание про однократность вызова resolve/reject - попытка повторного вызова не меняет состояние

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

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