До сих пор ты работал только с обычными значениями. Создавал переменную или константу, сохранял туда значение и мог сразу его использовать, например вывести на экран.
Но что делать, если значение появляется не сразу, а спустя какое-то время? Данные мы часто получаем из базы данных или внешнего сервера. Эти операции занимают какое-то время и есть два способа работать с ними:
- Мы можем попробовать заблокировать выполнение программы до тех пор, пока не получим данные.
- Или мы можем продолжить выполнение программы, а с данными разобраться когда они появятся.
Нельзя сказать, что один способ однозначно лучше, а другой хуже. В разных ситуациях нам нужно разное поведение.
Если данные, которые ты ждешь критически важны для продвижения дальше, то нужно блокироваться и ничего с этим не сделать. А если можно отложить их обработку, то конечно не стоит терять время, ведь можно сделать еще что-то полезное.
Что такое JavaScript Promise
Promise - это специальный тип объектов, который помогает работать с асинхронными операциями.
Многие функции будут тебе возвращать промис в ситуациях, когда значение невозможно получить сразу.
const userCount = getUserCount();
console.log(userCount); // Promise { <pending> }
В этом случае getUserCount - это функция, которая возвращает Promise. Если мы попробуем сразу же вывести на экран значение переменной userCount, то получим что-то вроде Promise { <pending> }.
Это произойдет потому, что данных еще нет и нам нужно их ждать.
Состояния Promise в JS
Промис может быть в нескольких состояниях:
- Pending - ответ еще не готов. Ждите.
- Fulfilled - ответ готов, успех. Заберите данные.
- Rejected - ответ готов, ошибка. Обработайте.
С состоянием pending мы не можем сделать ничего полезного, только ждать. А в других случаях, можем добавить функции обработчики, которые будут вызваны при переходе промиса в состояние fulfilled или rejected.
Для того, чтобы обработать успешное получение данных, нам нужна функция then.
const userCount = getUserCount();
const handleSuccess = (result) => {
console.log(`Promise was fulfilled. Result is ${result}`);
}
userCount.then(handleSuccess);
А для обработки ошибок - catch.
const handleReject = (error) => {
console.log(`Promise was rejected. The error is ${error}`);
}
userCount.catch(handleReject);
Обрати внимание, что функция getUserCount возвращает промис, поэтому мы не можем напрямую использовать userCount, а передаем в then и catch функции обработчики, которые будут вызваны в случае успеха или ошибки.
Функции then и catch можно вызвать последовательно. В таком случае мы учтем и ситуацию, когда все будет хорошо, и когда возникнет ошибка.
const userCount = getUserCount();
const handleSuccess = (result) => {
console.log(`Promise was fulfilled. Result is ${result}`);
}
const handleReject = (error) => {
console.log(`Promise was rejected. The error is ${error}`);
}
userCount.then(handleSuccess).catch(handleReject);
Обработка асинхронных ошибок в JavaScript Promise
Предположим, что у нас есть функция getUserData(userId), которая возвращает информацию о пользователе или бросает ошибку, если с параметром userId будут какие-то проблемы.
Раньше мы добавляли обычный try/catch и могли обработать ошибку в блоке catch.
try {
console.log(getUserData(userId));
} catch (e) {
handleError(e);
}
Но ошибки, которые возникают в асинхронном коде внутри промисов, невозможно поймать обычным try/catch.
Попробуем заменить блокирующую функцию getUserData(userId) , которая сразу возвращает результат на асинхронную fetchUserData(userId), которая возвращает промис.
Поведение мы хотим оставить тем же — вывести на экран результат в случае успеха, или обработать ошибку, если она возникнет.
try {
fetchUserData(userId).then(console.log);
} catch (e) {
handleError(e);
}
Но у нас это не получится. В основном, синхронном, коде ошибки нет, поэтому выполнение продолжится и дальше. Но когда в асинхронном коде возникнет необработанная ошибка, мы получим UnhandledPromiseRejection и наша программа завершится.
Для того, чтобы лучше понять порядок выполнения программы, добавим блок finally. Он выполнится в любом случае, но будет это до UnhandledPromiseRejection или после?
try {
fetchUserData(userId).then(console.log);
} catch (e) {
handleError(e);
} finally {
console.log('finally');
}
Разберемся пошагово:
- В блоке
tryмы вызываем функциюfetchUserData, которая возвращаетPromiseв состоянииpending - Блок
catchигнорируется, потому что в блокеtryошибок не было. Асинхронное выполнение пока никого не интересует! - На экран выводится строка
finally - Возникает ошибка в асинхронном коде и мы видим в консоли
UnhandledPromiseRejectionWarning
Чтобы не возникало необработанных ошибок в промисах, к ним нужно всегда добавлять обработчики в .catch().
fetchUserData(userId).then(console.log).catch(handleError);
Код стал короче, чище и мы избавились от неожиданных ошибок, которые могут сломать наш код.