Learn JavaScript

then() - обработка результата промиса в JavaScript

then() нужен, чтобы обработать результат промиса после успешного выполнения. Метод вызывается у объекта Promise, принимает функции-обработчики и возвращает новый промис, поэтому цепочки можно продолжать дальше.

Синтаксис

Базовая запись выглядит так:

promise.then(onFulfilled)

Или так:

promise.then(onFulfilled, onRejected)

Где:

  • promise - это объект Promise
  • onFulfilled - функция, которая выполнится, если промис успешно завершился
  • onRejected - необязательная функция, которая выполнится, если промис завершился с ошибкой

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

Что возвращает then()

then() всегда возвращает новый промис.

Это очень важный момент, потому что именно так работают цепочки:

const promise = Promise.resolve(5);
const nextPromise = promise.then((value) => value * 2);

console.log(nextPromise instanceof Promise); // true

nextPromise.then((value) => {
  console.log(value); // 10
});

Исходный промис и новый промис - это не одно и то же значение.

Простой пример

Promise.resolve("Данные загружены").then((result) => {
  console.log(result); // "Данные загружены"
});

Когда промис перейдёт в состояние fulfilled, функция внутри then() получит его результат.

then() не выполняется мгновенно

Даже если промис уже успешно завершён, обработчик из then() не запускается прямо в ту же секунду посреди синхронного кода.

const promise = Promise.resolve("ok");

console.log("start");

promise.then((value) => {
  console.log("then", value);
});

console.log("end");

Результат будет таким:

start
end
then ok

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

Как работает цепочка then()

Главная сила метода в том, что каждый then() создаёт новый промис и передаёт результат дальше по цепочке.

Если вернуть обычное значение

Тогда следующий then() получит именно это значение.

Promise.resolve(5)
  .then((value) => value * 2)
  .then((value) => {
    console.log(value); // 10
  });

Здесь первый then() получил 5, вернул 10, и это значение попало в следующий обработчик.

Если вернуть другой промис

Тогда следующий then() подождёт, пока этот промис завершится.

Promise.resolve(5)
  .then((value) => Promise.resolve(value * 3))
  .then((value) => {
    console.log(value); // 15
  });

Это позволяет строить длинные асинхронные цепочки из нескольких шагов.

Что будет, если выбросить ошибку

Если внутри then() возникает ошибка, новый промис из этой цепочки переходит в состояние rejected.

Promise.resolve(5)
  .then(() => {
    throw new Error("Что-то пошло не так");
  })
  .catch((error) => {
    console.log(error.message); // "Что-то пошло не так"
  });

Поэтому throw внутри then() автоматически превращается в ошибку следующего промиса.

Если не передать обработчик

Это ещё один полезный нюанс.

Если в then() не передать функцию, значение просто пойдёт дальше по цепочке.

Promise.resolve(5)
  .then()
  .then((value) => {
    console.log(value); // 5
  });

То же правило работает и для ошибок: если обработчик не передан, отклонённый промис пробрасывается дальше, пока его кто-то не обработает.

Чем второй аргумент then() отличается от catch()

Технически ошибку можно обработать и так:

Promise.reject(new Error("Ошибка"))
  .then(
    (value) => {
      console.log(value);
    },
    (error) => {
      console.log(error.message); // "Ошибка"
    },
  );

Но в реальном коде чаще пишут через catch():

Promise.reject(new Error("Ошибка"))
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.log(error.message); // "Ошибка"
  });

Так код обычно читается проще.

Есть и ещё один важный нюанс: второй аргумент then() не перехватывает ошибки, которые были выброшены внутри первого аргумента этого же then().

Promise.resolve(5)
  .then(
    () => {
      throw new Error("boom");
    },
    (error) => {
      console.log("Не сработает", error);
    },
  )
  .catch((error) => {
    console.log(error.message); // "boom"
  });

Именно поэтому для ошибок чаще рекомендуют отдельный catch().

then() не меняет исходный промис

Метод создаёт новый промис, но сам исходный объект не изменяет.

const promise = Promise.resolve(1);
const nextPromise = promise.then((value) => value + 1);

console.log(promise === nextPromise); // false

promise.then((value) => {
  console.log(value); // 1
});

nextPromise.then((value) => {
  console.log(value); // 2
});

Это важно, потому что один и тот же промис можно использовать в нескольких местах.

Когда это полезно

then() особенно удобен, когда:

  • нужно обработать результат асинхронной операции
  • вы хотите построить цепочку из нескольких шагов
  • нужно вернуть следующий промис и дождаться его завершения
  • код не хочется переводить в async/await, но результат всё равно нужно обработать

Пример:

function loadUser() {
  return Promise.resolve({ id: 1, name: "Анна" });
}

loadUser()
  .then((user) => user.name)
  .then((name) => {
    console.log(name); // "Анна"
  });

Частые ошибки

Думать, что then() возвращает не промис, а обычное значение

const result = Promise.resolve(5).then((value) => value * 2);

console.log(result instanceof Promise); // true

Результат then() - это всегда новый промис.

Ожидать, что обработчик сработает сразу

const promise = Promise.resolve("ok");

console.log("A");
promise.then((value) => console.log(value));
console.log("B");

Сначала выведется A, потом B, и только потом ok.

Забывать return внутри цепочки

Promise.resolve(5)
  .then((value) => {
    value * 2;
  })
  .then((value) => {
    console.log(value); // undefined
  });

Если внутри обработчика ничего не вернуть, дальше уйдёт undefined.

Обрабатывать ошибки вторым аргументом then() в длинных цепочках

Promise.resolve(5)
  .then(
    () => {
      throw new Error("boom");
    },
    (error) => {
      console.log(error);
    },
  )
  .catch((error) => {
    console.log(error.message); // "boom"
  });

Для читаемости и предсказуемой обработки ошибок чаще удобнее catch().

Быстрая памятка

  • then() вызывается у объекта Promise
  • первым аргументом передаётся обработчик успешного результата
  • вторым аргументом можно передать обработчик ошибки
  • then() всегда возвращает новый промис
  • если вернуть обычное значение, оно попадёт в следующий then()
  • если вернуть промис, цепочка подождёт его завершения
  • если выбросить ошибку, следующий промис станет rejected
  • если не передать обработчик, значение или ошибка пойдут дальше
  • сам исходный промис метод не изменяет

Коротко

then() в JavaScript нужен, чтобы обработать результат промиса и продолжить асинхронную цепочку. Главное, что стоит запомнить: метод всегда возвращает новый промис, не выполняется синхронно, умеет передавать дальше обычные значения и промисы, а для обработки ошибок в большинстве случаев удобнее использовать catch().