Ошибки в промисах

асинхронность≈ 7 минут

Здесь будет встроен видео-разбор темы (5–7 минут, YouTube / RuTube / VK Видео) — тем, кто лучше воспринимает на слух, текст ниже не обязателен.

Промис не бросает исключение так, как это делает обычный код: он молча переходит в состояние отклонения и ждёт обработчика. Найдётся catch — ошибка обработана, не найдётся — она утечёт в тишину.

Разделы темы:·

Цепочка промисов ведёт себя как эстафета: как только одно звено отклонилось, все следующие then пропускаются, и управление прыгает к ближайшему catch. Поэтому обработчик обычно ставят один — в самом конце.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Неважно, на каком звене случилась беда — первом или пятом. Ошибка «проваливается» вниз мимо всех успешных обработчиков и попадает в catch целой, вместе со своим сообщением.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Внутри then можно писать обычный throw — промис сам поймает исключение и превратит его в отклонение. Это удобно: проверки данных выглядят как в синхронном коде, а результат уезжает в catch.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

У then есть и второй аргумент — обработчик отклонения. Он ловит ошибку предыдущего звена, но не свою собственную: если бросить исключение в первом аргументе, соседний обработчик его не увидит. Это главная причина, почему catch в конце надёжнее.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Для цепочки — да. Оба способа переводят промис в отклонённое состояние с одной и той же причиной. Разница только в удобстве: throw короче и работает внутри условий, а Promise.reject пригодится там, где нужно вернуть значение выражением.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

catch — не конец цепочки, а такое же звено. Он возвращает промис, и после него цепочка продолжается уже как успешная. Это удобный способ «починить» ошибку и подставить запасное значение.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Обратная сторона: catch видит только то, что стоит выше него. Всё, что случится ниже — включая ошибку в самом обработчике, — потребует ещё одного catch. Поэтому финальный обработчик почти всегда идёт последним.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Если у отклонённого промиса так и не появился обработчик, ошибка никуда не денется — но и не остановит программу. Код спокойно идёт дальше, а браузер чуть позже сам сообщит о проблеме в консоль. Опасность именно в этом: приложение выглядит рабочим, хотя половина данных не загрузилась.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Последняя строка в панели — заслуга песочницы учебника: она слушает событие, о котором речь чуть ниже, и переносит сообщение к вам в вывод. На настоящей странице этой строки на экране нет: браузер пишет Uncaught (in promise) только в консоль инструментов разработчика, и пользователь о сбое не узнает.

На такие случаи есть глобальное событие unhandledrejection. Его ставят на весь сайт, чтобы отправлять пропущенные ошибки в систему логирования. Это диагностика, а не лечение: событие говорит, что где-то забыли catch. Вызов event.preventDefault() в обработчике отменяет стандартную реакцию браузера — ту самую строку в консоли: ошибку уже отправили куда надо, дублировать её незачем.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Строк в панели всё равно две, и первой успела песочница: она подписалась на unhandledrejection раньше вашего кода, а preventDefault() отменяет только реакцию браузера — чужих слушателей он не останавливает. В консоли настоящей страницы от этой ошибки осталась бы одна строка — «никто не обработал: нет токена».

Правило простое: у каждой цепочки есть хозяин — catch в конце или await внутри try. Всё остальное однажды утечёт в тишину.

Ничего не меняется, меняется только запись: отклонённый промис внутри await превращается в обычное исключение, и его ловит привычный try...catch. Забытый try даёт ровно то же необработанное отклонение.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

finally срабатывает при любом исходе и не получает аргументов — он не знает, успех был или ошибка. Его задача — уборка: выключить спиннер, разблокировать кнопку, закрыть соединение.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Главное свойство finally — прозрачность: он пропускает дальше исходный результат нетронутым. Значение остаётся значением, ошибка остаётся ошибкой и всё равно доезжает до catch, даже если finally стоит раньше него.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.
ЗвеноКогда сработаетЧто получаетЧто уходит дальше
then(onOk)только при успехезначението, что вернул обработчик
then(onOk, onErr)при успехе или ошибке вышезначение или ошибкурезультат сработавшего обработчика
catch(onErr)при любой ошибке вышеошибкууспех со значением из обработчика
finally(fn)всегданичегоисходное значение или ошибку без изменений

  • Отклонение пропускает все следующие then и прыгает к ближайшему catch — одного обработчика в конце обычно достаточно.
  • throw внутри then — обычный способ отклонить промис: исключение автоматически превращается в ошибку цепочки.
  • Второй аргумент then ловит ошибку предыдущего звена, но не свою собственную; отдельный catch надёжнее.
  • catch возвращает промис, поэтому после него цепочка продолжается как успешная — удобно подставлять запасное значение.
  • Цепочка без catch даёт unhandledrejection: страница не падает, но ошибка остаётся необработанной. finally выполняется всегда и ничего не меняет.

1/3Что выведет этот код? Promise.reject(new Error("A")).then(() => console.log("B")).catch((e) => console.log(e.message));

2/3У then есть второй аргумент — обработчик ошибки. Поймает ли он исключение, брошенное в первом аргументе того же then?

3/3Что окажется в консоли: Promise.resolve(1).finally(() => 99).then((v) => console.log(v));

Запасное значение вместо ошибки

Функция getSettings при неудаче отклоняется. Собери цепочку так, чтобы в консоль всегда печаталась тема: при успехе — полученная, при ошибке — light. Ожидаемый вывод: сначала dark, потом light.

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

Ошибку перехватывает catch, который возвращает объект с запасной темой. После него цепочка снова считается успешной, поэтому один общий then печатает результат обоих сценариев.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Скобки вокруг { theme: "light" } обязательны: без них стрелочная функция примет фигурные скобки за тело.

Почему catch не сработал

Автор поставил обработчик, но он не срабатывает: строки «поймали: …» в выводе нет, вместо неё — Uncaught (in promise) с той самой ошибкой. Найди причину и почини цепочку так, чтобы вывелось «поймали: не удалось удвоить 2».

js
Tab добавляет отступ. Чтобы выйти из редактора, нажмите Escape — фокус перейдёт на кнопку запуска.

catch видит только звенья, которые стоят выше него. Здесь он оказался раньше проблемного then, поэтому ошибке уже некуда было упасть. Достаточно поменять звенья местами.

jsлистинг · не запускается
Листинг только для чтения: этот код не запускается в браузере.

Заодно обрати внимание: значение 2 дошло до обработчика, потому что catch в исходной версии просто пропустил успешный результат дальше.