До сих пор ты работал только с обычными значениями. Создавал переменную или константу, сохранял туда значение и мог сразу его использовать, например вывести на экран.
Но что делать, если значение появляется не сразу, а спустя какое-то время? Данные мы часто получаем из базы данных или внешнего сервера. Эти операции занимают какое-то время и есть два способа работать с ними:
- Мы можем попробовать заблокировать выполнение программы до тех пор, пока не получим данные.
- Или мы можем продолжить выполнение программы, а с данными разобраться когда они появятся.
Нельзя сказать, что один способ однозначно лучше, а другой хуже. В разных ситуациях нам нужно разное поведение.
Если данные, которые ты ждешь критически важны для продвижения дальше, то нужно блокироваться и ничего с этим не сделать. А если можно отложить их обработку, то конечно не стоит терять время, ведь можно сделать еще что-то полезное.
Что такое 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);
Код стал короче, чище и мы избавились от неожиданных ошибок, которые могут сломать наш код.