Learn JavaScript

catch() - обработка ошибок промиса в JavaScript

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

Синтаксис

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

promise.catch(onRejected)

Где:

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

Обычно в onRejected передают объект ошибки или другое значение, с которым промис был отклонён.

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

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

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

const promise = Promise.reject(new Error("boom"));
const nextPromise = promise.catch((error) => error.message);

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

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

Результат catch() - не обычное значение, а новый промис.

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

Promise.reject(new Error("Не удалось загрузить данные"))
  .catch((error) => {
    console.log(error.message); // "Не удалось загрузить данные"
  });

Если промис переходит в состояние rejected, catch() получает причину ошибки.

catch() перехватывает отклонённый промис

Это базовый сценарий использования.

const promise = Promise.reject(new Error("Ошибка запроса"));

promise.catch((error) => {
  console.log(error.message); // "Ошибка запроса"
});

Если промис завершился с ошибкой, обработчик из catch() выполнится.

catch() перехватывает ошибки из предыдущего then()

Это ещё одна очень важная особенность.

catch() ловит не только уже отклонённые промисы, но и ошибки, которые были выброшены внутри предыдущих обработчиков then().

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

Поэтому catch() удобно ставить в конце цепочки, если нужно централизованно обработать ошибку.

Если в catch() вернуть значение, цепочка продолжится как успешная

Это один из самых полезных нюансов метода.

Если обработчик ошибки вернул обычное значение, следующий шаг получит его уже как успешный результат.

Promise.reject(new Error("boom"))
  .catch((error) => {
    console.log(error.message); // "boom"
    return "Запасной результат";
  })
  .then((value) => {
    console.log(value); // "Запасной результат"
  });

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

Если в catch() вернуть промис, цепочка подождёт его

Как и then(), метод умеет работать не только с обычным значением, но и с промисом.

Promise.reject(new Error("boom"))
  .catch((error) => {
    return Promise.resolve(error.message.toUpperCase());
  })
  .then((value) => {
    console.log(value); // "BOOM"
  });

Следующий then() выполнится только после завершения промиса, который был возвращён из catch().

Если в catch() выбросить ошибку, она пойдёт дальше

Иногда ошибку не нужно окончательно обрабатывать на текущем этапе. В таком случае её можно выбросить снова.

Promise.reject(new Error("boom"))
  .catch(() => {
    throw new Error("again");
  })
  .catch((error) => {
    console.log(error.message); // "again"
  });

То же самое произойдёт, если вернуть Promise.reject(...).

catch() не срабатывает у успешного промиса

Это полезно помнить, чтобы не ждать лишнего поведения.

Promise.resolve(1)
  .catch(() => {
    console.log("Не выполнится");
  })
  .then((value) => {
    console.log(value); // 1
  });

Если ошибки не было, catch() просто пропускается.

catch() - это сокращённая запись для then(undefined, onRejected)

По поведению эти варианты эквивалентны:

Promise.reject("x").then(undefined, (error) => {
  console.log("then", error); // "x"
});
Promise.reject("x").catch((error) => {
  console.log("catch", error); // "x"
});

Но catch() обычно читается проще, особенно в длинных цепочках.

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

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

const promise = Promise.reject(new Error("boom"));
const handled = promise.catch((error) => error.message);

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

handled.then((value) => {
  console.log(value); // "boom"
});

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

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

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

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

Пример:

function loadUser() {
  return Promise.reject(new Error("Пользователь не найден"));
}

loadUser()
  .catch((error) => {
    console.log(error.message); // "Пользователь не найден"
    return { id: 0, name: "Гость" };
  })
  .then((user) => {
    console.log(user.name); // "Гость"
  });

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

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

const result = Promise.reject(new Error("boom"))
  .catch((error) => error.message);

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

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

Ожидать, что catch() сработает без ошибки

Promise.resolve("ok")
  .catch((error) => {
    console.log(error);
  })
  .then((value) => {
    console.log(value); // "ok"
  });

Если промис завершился успешно, обработчик catch() не вызывается.

Забывать, что возврат из catch() восстанавливает цепочку

Promise.reject(new Error("boom"))
  .catch((error) => {
    console.log(error.message); // "boom"
    return "fixed";
  })
  .then((value) => {
    console.log(value); // "fixed"
  });

После return ошибка считается обработанной, и дальше цепочка идёт как успешная.

Думать, что catch() поймает вообще любую асинхронную ошибку

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

Здесь ошибка возникает уже внутри колбэка setTimeout(), то есть вне текущей цепочки промисов. Такой случай нужно обрабатывать отдельно.

Случайно проглатывать ошибку, когда её нужно пробросить дальше

Promise.reject(new Error("boom"))
  .catch((error) => {
    console.log(error.message); // "boom"
  })
  .then(() => {
    console.log("Цепочка пошла дальше как успешная");
  });

Если после обработки ошибки нужно оставить цепочку в состоянии ошибки, ошибку надо выбросить снова или вернуть Promise.reject(...).

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

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

Коротко

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