Предыдущая статья посвящалась практике применения промисов, но, при этом, в ней не была рассмотрена очень важная тема - обработка ошибок. Действительно, любое приложение должно корректно обрабатывать исключительные ситуации, и, при необходимости, уведомлять о них пользователя. Об этом и пойдет речь в данной статье. Мы рассмотрим такие ситуации, как отказ промисов, возникновение исключений в процессе выполнения промиса и в коллбеках onFulfilled и onRejected.

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



Как мы видели ранее "then" принимает два аргумента, один на случай успеха и другой, на случай неудачи(в терминологии промисов они называются "выполнение"(fulfill) и "отказ"(reject)):

get('story.json').then(function(response) {
console.log("Успех!", response);
}, function(error) {
console.log("Неудача!", error);
});

Так же можно использовать "catch":

get('story.json').then(function(response) {
console.log("Успех!", response);
}).catch(function(error) {
console.log("Неудача!", error);
});

Ничего особенного в "catch" нету, это просто синтаксический сахар для then(undefined, func), но он действительно лучше читается. Обратите внимание на то, что приведенные выше примеры не одинаковы, последний эквивалентен:

get('story.json').then(function(response) {
console.log("Успех!", response);
}).then(undefined, function(error) {
console.log("Неудача!", error);
});

Различие незаметное, но весьма полезное. В случаи отказа первый шаг пропускается, и сразу же запускается функция отказа второго "then" (или "catch", поскольку они эквивалентны). В случаи then(func1, func2), одна из func1, func2 будет вызвана, но никогда обе из них. Но в случаи then(func1).catch(func2), обе функции могут быть вызваны, если выполнение func1 приводит к отказу, поскольку это разные шаги в цепи. Рассмотрим следующее:

asyncThing1().then(function() {
return asyncThing2();
}).then(function() {
return asyncThing3();
}).catch(function(err) {
return asyncRecovery1();
}).then(function() {
return asyncThing4();
}, function(err) {
return asyncRecovery2();
}).catch(function(err) {
console.log("Не волнуйся об этом");
}).then(function() {
console.log("Все готово!");
});

Приведенный выше процесс выполнения очень похож на обычные конструкции try/catch в JavaScript, ошбки произошедшие в "try" попадают непосредственно в "catch" блок. Вот блок-схема этого процесс(потому, что я люблю блок-схемы):



Зелеными стрелками отмечено выполнение промисов, красными — отказ.

Исключения JavaScript и промисы



Отказы в промисах происходят когда явно вызвана функция-отказ, однако они так же происходят и в случаи выброса ошибки в ходе выполнения функции передаваемой в конструктор:

var jsonPromise = new Promise(function(resolve, reject) {
// JSON.parse выбрасывает ошибку, если ему в качестве параметра
// передается неверный JSON, так что это неявный отказ:
resolve(JSON.parse("Это не JSON"));
});

jsonPromise.then(function(data) {
// Эта функция не будет вызвана:
console.log("Сработало!", data);
}).catch(function(err) {
// Выполнится эта функция:
console.log("Все сломалось!", err);
});

Это означает, что всю Вашу работу связанную с промисами полезно делать внутри функции передаваемой в конструктор промиса, таким образом все ошибки автоматически будут обработаны и вызовут отказ.

То же каcается исключений возникших в функциях передаваемых в "then".

get('/').then(JSON.parse).then(function() {
// Эта функция никогда не вызывается, '/' — HTML страница, а не JSON
// таким образом JSON.parse выбрасывает ошибку
console.log("Сработало!", data);
}).catch(function(err) {
// Вместо этого выпонится:
console.log("Неудача!", err);
});


Обработка ошибок на практике



В нашем примере с историей и разделами, мы можем использовать catch для обработки и отображения ошибки пользователю:

getJSON('story.json').then(function(story) {
return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
addHtmlToPage(chapter1.html);
}).catch(function() {
addTextToPage("Не удалось отобразить раздел");
}).then(function() {
document.querySelector('.spinner').style.display = 'none';
});

Если получение story.chapterUrls[0] завершается неудачно (к примеру, ошибка http 500 или клиент не подключен к сети), то все обработчики успеха будут пропущены, включая обработчик внутри getJSON, который пытается сконвертировать полученный ответ в JSON и так же обработчик, размещающий chapter1.html на страницу. Вместо этого сразу же выполнится обработчик catch и на страницу будет выведено "Не удалось отобразить раздел".

Как и в привычном JavaScript'овом try/catch, если ошибка была обработана, то последующий код будет выполнен, соответственно индикатор загрузки будет скрыт в любом случаи, чего мы, собственно, и добивались. Предыдущий фрагмент является неблокирующейся асинхронной версией следующего:

try {
var story = getJSONSync('story.json');
var chapter1 = getJSONSync(story.chapterUrls[0]);
addHtmlToPage(chapter1.html);
}
catch (e) {
addTextToPage("Неудалось отобразить раздел");
}
document.querySelector('.spinner').style.display = 'none';

Вы можете использовать "catch" и с целью простого логирования, без восстановления от ошибок. Для этого, достаточно просто повторно выбросить ошибку "catch" блоке. Мы можем сделать это в методе getJSON:

function getJSON(url) {
return get(url).then(JSON.parse).catch(function(err) {
console.log("запрос getJSON выполнился неудачно для ", url, err);
throw err;
});
}

Итак, нам удалось получить один раздел, но мы бы хотели получить их все. Перейдем теперь к решению этой задачи.
0

Комментарии

Для того, чтоб оставлять комментарии или зарегистрируйтесь.