Vue Front-end Инженер

Vue Front-end Инженер

Роадмап навыков для прокачки

Состояния промиса

JavaScript (ES6 и новее)Асинхронное программированиеПромисы (Promise)

Основная идея

Promise имеет три состояния: pending (ожидание), fulfilled (успешно выполнен) и rejected (отклонён). Состояние меняется только один раз — после перехода в fulfilled или rejected промис становится "settled" и больше не изменяется.

Ключевые аспекты

  • pending — начальное состояние, операция выполняется
  • fulfilled — операция успешно завершена, вызывается resolve(value)
  • rejected — операция завершилась с ошибкой, вызывается reject(error)
  • settled — общий термин для fulfilled или rejected (промис завершён)
  • Неизменяемость — повторные вызовы resolve/reject игнорируются

Плюсы

  • Предсказуемое поведение — состояние меняется только один раз
  • Гарантия асинхронности — обработчики .then()/.catch() всегда асинхронны
  • Кеширование результата — можно подписаться на уже resolved промис
  • Композиция через Promise.all/race/allSettled

Минусы

  • Нельзя отменить pending-промис без внешних библиотек
  • Нет встроенного способа проверить текущее состояние промиса
  • Результат кешируется навсегда, даже если он больше не нужен

Частые ошибки на собеседованиях

  • Путают порядок выполнения: executor синхронный, а .then()/.catch() — асинхронные
  • Не знают, что промисы выполняются раньше setTimeout (микрозадачи vs макрозадачи)
  • Путают термины "resolved" и "fulfilled" — технически это разные понятия
  • Думают, что можно изменить состояние промиса после первого resolve/reject

Введение и проблематика

Promise в JavaScript — это объект-обёртка для асинхронной операции, результат которой ещё неизвестен. Понимание состояний промиса критически важно для правильной работы с асинхронным кодом, отладки и предотвращения распространённых ошибок.

Promise — это как "обещание" доставить результат в будущем. Курьер может быть в пути (pending), доставить посылку (fulfilled) или сообщить о проблеме (rejected).

Почему это важно?

  • Состояние промиса определяет, какой callback будет вызван
  • Неправильное понимание состояний приводит к утечкам памяти и гонкам
  • На собеседованиях часто спрашивают о жизненном цикле промиса

Базовая теория

Три состояния Promise

Promise всегда находится в одном из трёх состояний:

stateDiagram-v2 [*] --> Pending: new Promise() Pending --> Fulfilled: resolve(value) Pending --> Rejected: reject(error) Fulfilled --> [*]: .then(onFulfilled) Rejected --> [*]: .catch(onRejected)
СостояниеОписаниеПереход
pendingНачальное состояние, операция выполняетсяПри создании промиса
fulfilledОперация успешно завершенаПри вызове resolve(value)
rejectedОперация завершилась с ошибкойПри вызове reject(error) или при исключении
⚠️

Состояние промиса можно изменить только ОДИН раз. После перехода в fulfilled или rejected промис становится "settled" (завершённым) и больше не меняется.

Терминология

Code Example 1: Какое состояние у каждого из этих промисов? Что означает каждое состояние?

js
// Pending — ожидание
const pendingPromise = new Promise((resolve, reject) => {
  // Промис в состоянии pending, пока не вызван resolve или reject
});
 
// Fulfilled — выполнен успешно (resolved)
const fulfilledPromise = Promise.resolve('успех');
 
// Rejected — отклонён
const rejectedPromise = Promise.reject(new Error('ошибка'));
 
// Settled — любое завершённое состояние (fulfilled ИЛИ rejected)
// Используется в Promise.allSettled()

Практические примеры

Создание промиса и переходы между состояниями

Code Example 2: Какие состояния пройдёт этот промис? Какой callback будет вызван и почему?

Шаг 1: Создание промиса (pending)

js
const promise = new Promise((resolve, reject) => {
  console.log('Промис создан, состояние: pending');
 
  // Имитируем асинхронную операцию
  setTimeout(() => {
    const success = Math.random() > 0.5;
 
    if (success) {
      resolve('Данные получены'); // Переход в fulfilled
    } else {
      reject(new Error('Ошибка загрузки')); // Переход в rejected
    }
  }, 1000);
});

Шаг 2: Обработка результата

js
promise
  .then(value => {
    // Вызывается при fulfilled
    console.log('Fulfilled:', value);
  })
  .catch(error => {
    // Вызывается при rejected
    console.log('Rejected:', error.message);
  });

Проверка состояния промиса

К сожалению, JavaScript не предоставляет прямого способа проверить состояние промиса. Но можно использовать обходной путь:

Code Example 3: Как работает эта функция promiseState? Что она вернёт сразу после создания промиса и через 1.5 секунды?

js
function promiseState(promise) {
  const pending = { status: 'pending' };
 
  return Promise.race([promise, pending])
    .then(
      value => (value === pending) ? pending : { status: 'fulfilled', value },
      reason => ({ status: 'rejected', reason })
    );
}
 
// Использование
const myPromise = new Promise(resolve => setTimeout(() => resolve('done'), 1000));
 
promiseState(myPromise).then(console.log); // { status: 'pending' }
 
setTimeout(() => {
  promiseState(myPromise).then(console.log); // { status: 'fulfilled', value: 'done' }
}, 1500);

Неизменяемость состояния

Code Example 4: Что выведет console.log? Почему второй resolve и reject не работают?

js
const promise = new Promise((resolve, reject) => {
  resolve('первое значение');    // Промис переходит в fulfilled
  resolve('второе значение');    // ❌ Игнорируется!
  reject(new Error('ошибка'));   // ❌ Игнорируется!
});
 
promise.then(value => {
  console.log(value); // 'первое значение'
});

Визуализация жизненного цикла

sequenceDiagram participant Code as Код participant Promise participant Handler as .then() Code->>Promise: new Promise(executor) Note over Promise: pending Promise->>Promise: выполняется операция Promise->>Promise: resolve(value) Note over Promise: fulfilled Promise->>Handler: вызов onFulfilled(value) Handler-->>Code: результат обработки

Пограничные кейсы

🚫

Критично: Executor выполняется СИНХРОННО при создании промиса!

Code Example 5: В каком порядке выполнятся console.log? Почему?

js
console.log('1. До создания промиса');
 
const promise = new Promise((resolve, reject) => {
  console.log('2. Внутри executor'); // Выполняется СРАЗУ
  resolve('готово');
});
 
console.log('3. После создания промиса');
 
promise.then(value => {
  console.log('4. В обработчике:', value); // Выполняется асинхронно
});
 
console.log('5. После .then()');
 
// Вывод:
// 1. До создания промиса
// 2. Внутри executor
// 3. После создания промиса
// 5. После .then()
// 4. В обработчике: готово
⚠️

Обработчики .then()/.catch() ВСЕГДА выполняются асинхронно, даже если промис уже fulfilled!

Code Example 6: В каком порядке выполнятся console.log, учитывая что промис уже resolved?

js
const resolved = Promise.resolve('уже готово');
 
resolved.then(value => {
  console.log('2.', value); // Выполнится ПОСЛЕ console.log('1.')
});
 
console.log('1. Синхронный код');
 
// Вывод:
// 1. Синхронный код
// 2. уже готово

Promise.resolve() и thenable-объекты

Code Example 7: Что вернёт Promise.resolve() в каждом из трёх случаев? Что такое thenable?

js
// Promise.resolve() с обычным значением
Promise.resolve(42); // fulfilled со значением 42
 
// Promise.resolve() с промисом — возвращает тот же промис
const p = Promise.resolve('test');
Promise.resolve(p) === p; // true
 
// Promise.resolve() с thenable — "разворачивает" его
const thenable = {
  then(resolve) {
    resolve('из thenable');
  }
};
Promise.resolve(thenable).then(console.log); // 'из thenable'

Продвинутые аспекты

Promise.allSettled() — работа с settled-состоянием

Code Example 8: Что выведет этот код? Чем Promise.allSettled отличается от Promise.all?

js
const promises = [
  Promise.resolve('успех'),
  Promise.reject(new Error('ошибка')),
  Promise.resolve('ещё успех')
];
 
// Promise.allSettled ждёт ВСЕ промисы (и fulfilled, и rejected)
Promise.allSettled(promises).then(results => {
  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log('Успех:', result.value);
    } else {
      console.log('Ошибка:', result.reason.message);
    }
  });
});
 
// Вывод:
// Успех: успех
// Ошибка: ошибка
// Успех: ещё успех

Микрозадачи и порядок выполнения

Code Example 9: В каком порядке выполнятся console.log? Почему Promise выполняется раньше setTimeout?

js
console.log('1. Синхронный');
 
setTimeout(() => console.log('4. setTimeout'), 0);
 
Promise.resolve()
  .then(() => console.log('3. Микрозадача'));
 
console.log('2. Синхронный');
 
// Вывод:
// 1. Синхронный
// 2. Синхронный
// 3. Микрозадача (очередь микрозадач выполняется раньше setTimeout)
// 4. setTimeout

Плюсы и минусы

АспектОписание
✅ ПредсказуемостьСостояние меняется только один раз
✅ Гарантия асинхронностиОбработчики всегда асинхронны
✅ КомпозицияPromise.all/race/allSettled для работы с группами
⚠️ Нет отменыНельзя отменить pending-промис
⚠️ Нет проверкиНельзя напрямую узнать текущее состояние
⚠️ ОдноразовостьРезультат кешируется навсегда

Вопросы интервьюера

Q: Какие три состояния есть у Promise?

pending (ожидание), fulfilled (успешно выполнен), rejected (отклонён). После перехода в fulfilled или rejected промис называется "settled".

Q: Можно ли изменить состояние промиса после resolve()?

Нет. Состояние меняется только один раз. Повторные вызовы resolve() или reject() игнорируются.

Q: Когда выполняется executor функция?

Синхронно, сразу при создании промиса через new Promise(). Но обработчики .then()/.catch() всегда асинхронны.

Q: Чем settled отличается от resolved?

Settled — промис в состоянии fulfilled ИЛИ rejected (завершён). Resolved — часто используется как синоним fulfilled, хотя технически "resolved" означает, что промис связан с другим промисом или значением.

Q: В каком порядке выполняются промисы и setTimeout?

Промисы добавляются в очередь микрозадач, которая выполняется раньше макрозадач (setTimeout). Поэтому .then() сработает раньше setTimeout с нулевой задержкой.


Источники

Code Example 1: Promise states

❓ Какое состояние у каждого из этих промисов? Что означает каждое состояние?

js
const pendingPromise = new Promise((resolve, reject) => {
});
 
const fulfilledPromise = Promise.resolve('успех');
 
const rejectedPromise = Promise.reject(new Error('ошибка'));

Code Example 2: Creating promise and state transitions

❓ Какие состояния пройдёт этот промис? Какой callback будет вызван и почему?

js
const promise = new Promise((resolve, reject) => {
  console.log('Промис создан, состояние: pending');
 
  setTimeout(() => {
    const success = Math.random() > 0.5;
 
    if (success) {
      resolve('Данные получены');
    } else {
      reject(new Error('Ошибка загрузки'));
    }
  }, 1000);
});
 
promise
  .then(value => {
    console.log('Fulfilled:', value);
  })
  .catch(error => {
    console.log('Rejected:', error.message);
  });

Code Example 3: promiseState utility

❓ Как работает эта функция promiseState? Что она вернёт сразу после создания промиса и через 1.5 секунды?

js
function promiseState(promise) {
  const pending = { status: 'pending' };
 
  return Promise.race([promise, pending])
    .then(
      value => (value === pending) ? pending : { status: 'fulfilled', value },
      reason => ({ status: 'rejected', reason })
    );
}
 
const myPromise = new Promise(resolve => setTimeout(() => resolve('done'), 1000));
 
promiseState(myPromise).then(console.log);
 
setTimeout(() => {
  promiseState(myPromise).then(console.log);
}, 1500);

Code Example 4: Immutability of state

❓ Что выведет console.log? Почему второй resolve и reject не работают?

js
const promise = new Promise((resolve, reject) => {
  resolve('первое значение');
  resolve('второе значение');
  reject(new Error('ошибка'));
});
 
promise.then(value => {
  console.log(value);
});

Code Example 5: Executor execution order

❓ В каком порядке выполнятся console.log? Почему?

js
console.log('1. До создания промиса');
 
const promise = new Promise((resolve, reject) => {
  console.log('2. Внутри executor');
  resolve('готово');
});
 
console.log('3. После создания промиса');
 
promise.then(value => {
  console.log('4. В обработчике:', value);
});
 
console.log('5. После .then()');

Code Example 6: .then() always async

❓ В каком порядке выполнятся console.log, учитывая что промис уже resolved?

js
const resolved = Promise.resolve('уже готово');
 
resolved.then(value => {
  console.log('2.', value);
});
 
console.log('1. Синхронный код');

Code Example 7: Promise.resolve() with different values

❓ Что вернёт Promise.resolve() в каждом из трёх случаев? Что такое thenable?

js
// Case 1:
Promise.resolve(42);
 
// Case 2:
const p = Promise.resolve('test');
Promise.resolve(p) === p;
 
// Case 3:
const thenable = {
  then(resolve) {
    resolve('из thenable');
  }
};
Promise.resolve(thenable).then(console.log);

Code Example 8: Promise.allSettled()

❓ Что выведет этот код? Чем Promise.allSettled отличается от Promise.all?

js
const promises = [
  Promise.resolve('успех'),
  Promise.reject(new Error('ошибка')),
  Promise.resolve('ещё успех')
];
 
Promise.allSettled(promises).then(results => {
  results.forEach(result => {
    if (result.status === 'fulfilled') {
      console.log('Успех:', result.value);
    } else {
      console.log('Ошибка:', result.reason.message);
    }
  });
});

Code Example 9: Microtasks vs setTimeout

❓ В каком порядке выполнятся console.log? Почему Promise выполняется раньше setTimeout?

js
console.log('1. Синхронный');
 
setTimeout(() => console.log('4. setTimeout'), 0);
 
Promise.resolve()
  .then(() => console.log('3. Микрозадача'));
 
console.log('2. Синхронный');