От колбэков к промисам: then, catch, finally

Зачем придумали Promise и как им пользоваться.

Promise — объект, представляющий результат асинхронной операции, который появится в будущем. Имеет три состояния: pending (ожидание), fulfilled (успех), rejected (ошибка).

Проблема колбэков

Раньше асинхронность строили на колбэках. Вложенные операции превращались в «callback hell» — лесенку из вложенных функций, которую тяжело читать и в которой легко потерять обработку ошибок.

getUser(id, (user) => {
  getOrders(user, (orders) => {
    getDetails(orders, (details) => {
      // лесенка растёт вправо
    });
  });
});

Промис: then и catch

Промис позволяет выстроить операции в плоскую цепочку. .then обрабатывает успех, .catch — ошибку для всей цепочки сразу.

const promise = new Promise((resolve) => {
  resolve(10); // успех со значением 10
});

promise
  .then((value) => {
    console.log("получили:", value);
    return value * 2;       // передаём дальше
  })
  .then((doubled) => {
    console.log("удвоили:", doubled);
  });

Вывод:

получили: 10
удвоили: 20

Обработка ошибок

Если промис отклонён (reject) или в .then выброшено исключение, управление прыгает в ближайший .catch. .finally выполняется в любом случае.

Promise.reject(new Error("сбой сети"))
  .then(() => console.log("не выполнится"))
  .catch((err) => console.log("поймали:", err.message))
  .finally(() => console.log("finally всегда"));

Вывод:

поймали: сбой сети
finally всегда

Цепочка значений

Каждый .then возвращает новый промис. Если вернуть значение — оно станет результатом следующего .then; если вернуть промис — цепочка дождётся его.

Promise.resolve(1)
  .then((x) => x + 1)
  .then((x) => Promise.resolve(x * 10)) // вернули промис
  .then((x) => console.log("итог:", x));

Вывод:

итог: 20

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

СостояниеЧто значитОбработчик
pendingоперация идёт
fulfilledуспех.then
rejectedошибка.catch

Важное свойство, о котором спрашивают: промис неизменяем после разрешения. Как только он стал fulfilled или rejected, его состояние и значение зафиксированы навсегда — повторный resolve ничего не изменит. Поэтому к уже выполненному промису можно добавить .then сколько угодно раз, и он сразу получит сохранённый результат.

Частая ошибка: «забыли вернуть промис»

Типичный баг, который любят показывать на собеседовании: внутри .then вызвали асинхронную операцию, но не вернули её. Тогда цепочка не дождётся вложенного промиса, и порядок «поедет». Правило простое: если в .then вы запускаете что-то асинхронное — обязательно return этот промис, чтобы внешняя цепочка его дождалась. Это же правило лежит в основе того, почему async/await (следующий урок) читается проще: там ожидание выражается явным await.

Итог

  • Промис избавляет от вложенных колбэков, выстраивая плоскую цепочку.
  • .then — успех, .catch — ошибка всей цепочки, .finally — всегда.
  • Каждый .then возвращает промис; вернёшь промис — цепочка его дождётся.
Проверьте себя
1. Сколько состояний у промиса?
AДва: успех и ошибка
BТри: pending, fulfilled, rejected
CОдно
DЧетыре
2. Когда выполнится коллбэк .finally?
AТолько при успехе
BТолько при ошибке
CВ любом случае — и при успехе, и при ошибке
DНикогда, если был .catch
3. Что произойдёт, если в .then вернуть другой промис?
AЦепочка проигнорирует его
BСледующий .then дождётся результата этого промиса
CБудет ошибка
DПромис превратится в строку
Поддержать проект