Работая с JavaScript или NodeJS важно понимать как себя ведут «Отклонённые Обещания». Я говорю о таких объектах Promise, где в переданной функции «Исполнитель» была вызвана функция отклонения — reject(). То есть когда ветвление алгоритма привело нас к reject(), а не к resolve().
Называть функцию «Исполнитель» мы можем как угодно (здесь мы используем стрелочную безымянную функцию). Имена функций «разрешения» или «отклонения» могут тоже называться как угодно. Мы стараемся придерживаться адекватного именования:
- resolve — разрешает
- reject — отклоняет
Подробно ознакомиться с объектами Обещаний можно в стандарте ECMAScript — https://tc39.es/ecma262/#sec-promise-objects.
Видеоролик на тему отклонённых обещаний
async await только с отклонённым обещанием без метода catch() и без метода then()
Давайте смотреть на самый простой пример № 1. Здесь мы используем только одно Обещание!
let pr1 = new Promise((resolve, reject)=>{reject('qwe')}); async function f1(pr){ return await pr }; let t1 = await f1(pr1);
Что мы пытаемся сделать? Мы создаём такое Обещание «pr1«, которое 100% отклоняется в момент вызова. В функцию отклонения мы передаём строку ‘qwe‘, чтобы в будущем с ней как-то взаимодействовать. Мы хотим, чтобы так было.
Затем мы объявляем асинхронную функцию «f1«, которая будет ждать выполнения нашего Обещания. И далее мы хотим присвоить результат выполнения Обещания в переменную «t1» — условно text1.
Что у нас получится при вызове этого алгоритма?
Весь наш алгоритм завершился внезапно из-за ошибки — «Uncaught qwe«. Из-за этого, переменная «t1» вообще не создалась так как программа вышла из выполнения на уровне вызова асинхронной функции. Вызов await начался, но из-за ошибки внезапно завершился и объявление с присваиванием не выполнилось.
Какой вывод можно сделать из этой ситуации? Чистое Обещание без дополнительных методов обработки опасно для работы приложения. Асинхронная функция, ожидающая результат работы Обещания, не будет выполнена. async await в этом случае не сработает как должен.
async await с отклонённым обещанием и с методом catch(), но без метода then()
Как правильно работать с отклонённым обещанием? Нам нужно научиться перехватывать ошибки у отклонённых обещаний. Главное, что в стандартном наборе среды выполнения JavaScript кода, этот функционал есть. Я говорю о методе catch(). Его основное отличие от метода then() в том, что он умеет обрабатывать только отклонённые обещания!
Что важно знать? Метод catch() вызывается на каком-то объекте Обещания и всегда возвращает нам новый объект Обещания. Посмотрим на пример № 2.
let pr2 = new Promise((resolve, reject)=>{reject('qwe')}); pr2.constructor.name; // 'Promise' let c2 = pr2.catch(); c2.constructor.name; // 'Promise'
Результат последовательного выполнения данных команд:
Можем выполнить сравнение объектов и убедиться, что это разные объекты:
pr2 == c2 false pr2 === c2 false
Обратите внимание, в метод catch() мы не передали никакую функцию, которая бы обрабатывала результат отклонения нашего основного Обещания. То есть мы никак не работаем с отклонением. Давайте это исправим в примере № 3.
let pr3 = new Promise((resolve, reject)=>{reject('qwe')}); let c3 = pr3.catch(o => 'Перехватили результат отклонения: ' + o); let t3 = await c3;
Что получим на этот раз? Я работаю в консоли браузера на самом верхнем уровне видимости.
Обратите внимание на этот пример! Он наиболее показательный:
Перехват отклонённого обещания со статусом «rejected» вернул новое обещание со статусом «fulfilled«. То есть метод catch() не только создал второе Обещание, но он также смог изменить статус у этого Обещания на «выполненное — разрешённое». Вместо ошибки мы получили полезную информацию. Программа не сломалась.
В этот раз мы использовали передачу параметра в метод catch() — это стрелочная функция «(o) => ‘Перехватили результат отклонения: ‘ + o«.
async await с отклонённым обещанием и с методом then(), но без метода catch()
С отклонённым обещанием можно также работать и через метод then(). С ним мы тоже сможем перехватывать ошибки у отклонённых Обещаний. Его основное отличие от метода catch() в том, что он умеет одновременно обрабатывать разрешённые и отклонённые обещания! Он умеет принимать функцию обработки разрешения и функцию обработки отклонения.
Обязательно различайте смысл работы 4 функций:
- resolve — разрешает Обещание (делает Обещание разрешённым)
- reject — отклоняет Обещание (делает Обещание отклонённым)
- onFulfilled — обрабатывает результат разрешённого Обещания
- onRejected — обрабатывает результат отклонённого Обещания
То есть две функции влияют на присвоение статуса Обещания, а две другие знают что в этом случае нужно делать (при том или ином статусе Обещания). Генераторы и Обработчики.
Пример № 5
let pr5 = new Promise((resolve, reject)=>{reject('qwe')}); let c5 = pr5.then(undefined, o=>'Перехватили результат отклонения в then(): ' + o); let t5 = await c5;
Мы передаём в метод then() только обработчик отклонённого Обещания. Для разрешённого Обещания у нас нет функции-обработчика — undefined.
Как писать код при работе с отклонёнными обещаниями в JavaScript?
Общая рекомендация такая. Результат разрешения и результат отклонения должны иметь одинаковую структуру данных. Если вы ждёте от обещания строку, то в случае отклонения тоже должна перехватываться строка. Если вы ждёте от обещания объект, то в случае отклонения тоже должен перехватываться объект. И так далее.
Функция «Исполнитель» в желаемом Обещании может иметь сильное ветвление логики работы. Не забывайте везде разрешать или отклонять Обещание в каждом из блоков ветвления. Если вы пропустите какой-то блок ветвления алгоритма, то вы можете получить Обещание, которое ожидает выполнения — это Обещание со статусом «pending«. Это очень опасно при сборке динамических данных из разных баз данных. Если какой-то фрагмент данных не приедет, то всё приложение может повиснуть.