Цепочки обещаний отлично справляются с обработкой ошибок. Когда обещание отклоняется, элемент управления переходит к ближайшему обработчику отклонения. Это очень удобно на практике.
Например, в приведенном ниже коде URL-адрес для fetch
неверен (такого сайта нет), и .catch
обрабатывает ошибку:
fetch('https://no- such-server.blabla') // отклоняет .then(ответ => ответ.json()) .catch(err => alert(err)) // Ошибка типа: не удалось получить (текст может отличаться)
Как видите, .catch
не обязательно должен быть немедленным. Оно может появиться после одного или нескольких .then
.
Или, может быть, с сайтом все в порядке, но ответ не валидный JSON. Самый простой способ отловить все ошибки — добавить .catch
в конец цепочки:
fetch('https://javascript.info/article/promise-chaining/user.json') .then(ответ => ответ.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(ответ => ответ.json()) .then(githubUser => новое обещание((разрешить, отклонить) => { пусть img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "обещание-аватар-пример"; document.body.append(img); setTimeout(() => { img.remove(); разрешить (githubUser); }, 3000); })) .catch(ошибка => оповещение(ошибка.сообщение));
Обычно такой .catch
вообще не срабатывает. Но если какое-либо из приведенных выше обещаний отклоняется (проблема с сетью, неверный json или что-то еще), тогда оно его поймает.
В коде исполнителя обещаний и обработчиков обещаний есть «невидимый try..catch
». Если случается исключение, оно перехватывается и обрабатывается как отклонение.
Например, этот код:
новое обещание((решить, отклонить) => { выдать новую ошибку("Упс!"); }).catch(предупреждение); // Ошибка: Упс!
…Работает точно так же:
новое обещание((решить, отклонить) => { отклонить (новая ошибка («Упс!»)); }).catch(предупреждение); // Ошибка: Упс!
«Невидимый try..catch
» вокруг исполнителя автоматически улавливает ошибку и превращает ее в отклоненное обещание.
Это происходит не только в функции-исполнителе, но и в ее обработчиках. Если мы throw
внутрь обработчика .then
, это означает отклоненное обещание, поэтому элемент управления перейдет к ближайшему обработчику ошибок.
Вот пример:
новое обещание((решить, отклонить) => { решить("ок"); }).then((результат) => { выдать новую ошибку("Упс!"); // отклоняем обещание }).catch(предупреждение); // Ошибка: Упс!
Это происходит со всеми ошибками, а не только с теми, которые вызваны инструкцией throw
. Например, ошибка программирования:
новое обещание((решить, отклонить) => { решить("ок"); }).then((результат) => { блабла(); // нет такой функции }).catch(предупреждение); // Ошибка ссылки: блабла не определена
Последний .catch
перехватывает не только явные отклонения, но и случайные ошибки в обработчиках выше.
Как мы уже заметили, .catch
в конце цепочки аналогичен try..catch
. Мы можем иметь столько обработчиков .then
сколько захотим, а затем использовать один .catch
в конце для обработки ошибок во всех из них.
В обычной try..catch
мы можем проанализировать ошибку и, возможно, повторно выдать ее, если ее невозможно обработать. То же самое возможно и с обещаниями.
Если мы throw
внутрь .catch
, то управление перейдет к следующему ближайшему обработчику ошибок. И если мы обработаем ошибку и закончим нормально, то она продолжится до следующего ближайшего успешного обработчика .then
.
В приведенном ниже примере .catch
успешно обрабатывает ошибку:
// выполнение: catch -> then новое обещание((решить, отклонить) => { выдать новую ошибку("Упс!"); }).catch(функция(ошибка) { alert("Ошибка устранена, продолжайте как обычно"); }).then(() => alert("Следующий успешный запуск обработчика");
Здесь блок .catch
завершается нормально. Итак, вызывается следующий успешный обработчик .then
.
В примере ниже мы видим другую ситуацию с .catch
. Обработчик (*)
перехватывает ошибку и просто не может ее обработать (например, он знает только, как обрабатывать URIError
), поэтому выдает ее снова:
// выполнение: catch -> catch новое обещание((решить, отклонить) => { выдать новую ошибку("Упс!"); }).catch(function(error) { // (*) если (ошибка экземпляра URIError) { // справиться с этим } еще { alert("Невозможно обработать такую ошибку"); ошибка выброса; // выбрасывая ту или иную ошибку, переходим к следующему улову } }).then(function() { /* здесь не запускается */ }).catch(error => { // (**) alert(`Произошла неизвестная ошибка: ${error}`); // ничего не возвращаем => выполнение происходит обычным образом });
Выполнение переходит от первого .catch
(*)
к следующему (**)
по цепочке.
Что происходит, когда ошибка не обрабатывается? Например, мы забыли добавить .catch
в конец цепочки, как здесь:
новое обещание(функция() { нетТакаяФункция(); // Здесь ошибка (нет такой функции) }) .then(() => { // успешные обработчики обещаний, один или несколько }); // без .catch в конце!
В случае ошибки обещание отклоняется, и выполнение должно перейти к ближайшему обработчику отклонения. Но его нет. Таким образом, ошибка «застревает». Нет кода, который мог бы справиться с этим.
На практике, как и в случае с обычными необработанными ошибками в коде, это означает, что что-то пошло не так.
Что происходит, когда возникает обычная ошибка и ее не обнаруживает try..catch
? Скрипт завершается с сообщением в консоли. То же самое происходит и с необработанными отклонениями обещаний.
Механизм JavaScript отслеживает такие отклонения и в этом случае генерирует глобальную ошибку. Вы можете увидеть это в консоли, если запустите приведенный выше пример.
В браузере мы можем отловить такие ошибки, используя событие unhandledrejection
:
window.addEventListener('unhandledrejection', function(event) { // объект события имеет два специальных свойства: оповещение(событие.обещание); // [объект Promise] — обещание, вызвавшее ошибку оповещение(событие.причина); // Ошибка: Упс! - необработанный объект ошибки }); новое обещание(функция() { выдать новую ошибку("Упс!"); }); // нет подхвата для обработки ошибки
Событие является частью стандарта HTML.
Если происходит ошибка и нет .catch
, срабатывает обработчик unhandledrejection
и получает объект event
с информацией об ошибке, чтобы мы могли что-то сделать.
Обычно такие ошибки неустранимы, поэтому лучший выход — сообщить пользователю о проблеме и, возможно, сообщить об инциденте на сервер.
В небраузерных средах, таких как Node.js, существуют другие способы отслеживания необработанных ошибок.
.catch
обрабатывает ошибки в обещаниях любого типа: будь то вызов reject()
или ошибка, возникающая в обработчике.
.then
также перехватывает ошибки таким же образом, если ему присвоен второй аргумент (который является обработчиком ошибок).
Мы должны размещать .catch
именно там, где мы хотим обрабатывать ошибки и знаем, как их обрабатывать. Обработчик должен анализировать ошибки (в этом помогают пользовательские классы ошибок) и повторно выдавать неизвестные (возможно, это ошибки программирования).
Можно вообще не использовать .catch
, если нет возможности исправить ошибку.
В любом случае у нас должен быть обработчик событий unhandledrejection
(для браузеров и аналоги для других сред), чтобы отслеживать необработанные ошибки и сообщать о них пользователю (и, возможно, нашему серверу), чтобы наше приложение никогда не «просто умирало».
Что вы думаете? Сработает ли .catch
? Поясните свой ответ.
новое обещание (функция (разрешить, отклонить) { setTimeout(() => { выдать новую ошибку("Упс!"); }, 1000); }).catch(предупреждение);
Ответ: нет, не будет :
новое обещание (функция (разрешить, отклонить) { setTimeout(() => { выдать новую ошибку("Упс!"); }, 1000); }).catch(предупреждение);
Как сказано в главе, в коде функции существует «неявная try..catch
». Таким образом, все синхронные ошибки обрабатываются.
Но здесь ошибка генерируется не во время работы исполнителя, а позже. Итак, обещание не может справиться с этим.