Promise - это специальный объект, который гарантирует нам выполнение какого-то кода, но потом, по какому-то условия через определенное время. Как правило он используется при обращении к серверу, когда мы отправляем запрос и хотим сделать что-то с данными, которые придут в ответе.
Ранее, до появления промисов в es6, в нативном виде использовался XHR запрос, который был очень громоздким и для обработки ответа от сервера приходилось писать множество callback функций (существует даже термин callback hell), что значительно усложняло написание и восприятие кода. Промисы же как раз позволяют избежать огромной вложенности колбеков благодаря цепочкам последовательных обработок, но обо всем по порядку.
Создание промиса
Чтобы создать промисы мы должны воспользоваться конструктором new Promise и передать в него функцию, которая имеет два аргумента resolve и reject (это функции колбеки, они уже созданы в JS, их не нужно создавать или описывать, лишь только использовать) и непосредственно само тело функции, которое нам и нужно описать.
const myPromise = new Promise((resolve, reject) => {
// Ваш код здесь
})
resolve - данную колбек функцию мы должны будем вызвать где-то внутри тела и передать какой-то параметр - это может быть какой-то примитив либо другой промис. После вызова resolve промис будет считаться успешно выполненным.
reject - данную колбек функцию мы также должны вызвать где-то внутри и передать какой-то параметр. После вызова функции reject промис считается выполненным неудачно.
Promise state (Состояние)
Promise имеет три состояния:
pending (ожидание - когда промис просто создан, но еще не разрешился)
fulfilled (выполнен: промис разрешился успешно)
rejected (отклонен: пропис разрешился неудачно)
Состояние меняется только один раз, повторные изменения ни к чему не приведут (подобно return у функций), при этом не важно был промис выполнен удачно или отклонен по итогу он считается разрешенным.
Рассмотрим пример.
const age = 18;
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
if (age >= 18) {
resolve('Access is allowed');
}
else {
reject('Access denied');
}
}, 1000);
})
console.log(myPromise);
В данном случае задержка от сервера заменена обычным setTimeout, в котором идет проверка есть пользователю 18 лет или нет, после чего, спустя 1 секунду, будет выбран либо успешный, либо неудачный сценарий выполнения промиса.
Если мы выведем наш промис в консоль и быстро раскроем его значение (до того как он будет разрешен), то увидим следующую картину:
Наш промис изначально находится в режиме ожидания, у него нет никакого результата. Однако, если мы повторном выведем его в консоль, спустя 1 секунду, то увидим следующее:
Наш промис разрешился удачно, результатом будет значение, переданное в функцию resolve(). Тоже самое мы можем увидеть, если после загрузки страницы раскроем объект промиса не сразу, а через 1 секунду:
Мы увидим актуальное значение на момент раскрытия объекта в инструментах разработчика.
Чтобы увидеть неудачное разрешение промиса, заменим возраст на число меньше 18 и посмотрим на результат вывода в консоль.
Promise chaining (цепочки)
Хорошо, мы поняли, что промис имеет состояния и может выполниться успешно или неуспешно, но, что с этой информацией делать дальше? Как мы можем обработать результат промиса?
Для обработки результата у нас есть 3 метода: then, catch, finally.
then - как правило служит для обработки успешный промисов (например, вывести список постов после получения с сервера).
catch - для обработки ошибок (например, сообщение об ошибке связи с сервером)
finally - выполняется в любом случае (например, можно использовать для удаления лоадера)
Теперь разберем более подробно каждый метод, для этого создадим небольшую основу, эмулируя запрос на сервер с целью получения содержимого поста. Если будет запрос с id под номером 1, то мы будем получать текст поста, если же номер будет другим, то промис будет завершаться неудачно и результатом будет текст ошибки.
const postId = 1;
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => {
if (postId === 1) {
resolve('Текст поста под номером один.');
}
else {
reject('Пост не найден.');
}
}, 1000);
})
*then *
Данный метод обрабатывает результат промиса и имеет 2 аргумента - это функции: первая выполняется, когда промис разрешается успешно, вторая, обрабатывает неуспешно разрешенный промис и является необязательной.
myPromise
.then((success) => console.log(success), (error) => console.error(error))
Если мы оставим id равным 1, то получим результат работы функции success
Если же сменим id на 2, то получим результат работы функции error
Но на практике второй аргумент практически не используется, для того чтобы перехватить ошибки существует следующий метод.
catch
Данный метод принимает всего один аргумент - результат ошибки в ходе выполнения промиса. Давайте перепишем наш пример, используя данный метод.
myPromise
.then((success) => console.log(success))
.catch((error) => console.error(error));
В некоторых источниках указано, что catch - это аналог второго аргумента метода then, но на самом деле это не так. Второй аргумент из метода then перехватит ошибку из промиса тут все верно, но зато не перехватит ошибку, которая может возникнуть внутри обработчика, который стоит в первом аргумента, а ведь ошибка может возникнуть не только в процессе выполнения промиса, но также и в процессе обработки успешно завершенного промиса. Зато catch будет отлавливать любую ошибку, на любом этапе, а также после catch могут идти следующий обработчики, которые обработают код внутри блока catch. То есть catch не финальный обработчик, дальше может что-то еще идти и именно из всех этих перечисленных нюансов его использование методов then + catch более логично, нежели использование метода then с двумя функциями-обработчиками.
finally
Данный метод не похож на предыдущие: он ничего не принимает и ничего не возвращает. Он просто выполняет код внутри себя и обычно располагается в конце цепочки. Но если же после него расположены еще другие методы .then или .catch, то он просто пропустит значение сквозь себя. Как правило он используется для остановки лоадера, логирования информации и тд
Promise API
Для работы с промисами также есть специальные методы, которые позволяют работать с группой промисов и обрабатывать их более гибко.
Promise.all
Вначале немного про сам метод. Метод Promise.all принимает массив промисов (может принимать любой перебираемый объект, но обычно используется массив) и возвращает новый промис.
Новый промис завершится, когда завершится весь переданный список промисов, и его результатом будет массив их результатов.
Например у нас есть 3 промиса:
Ответ придет тогда, когда завершится последний промис, а ответом будет массив результатов промисов в том же порядке и не важно какой из промисов оказался быстрее.
Вот только если в каком-то промисе будет ошибка, то на выходе мы не получим массива с результатами, а получим просто ошибку.
Promise.race
Этот метод очень похож на Promise.all, но дожидается просто первого промиса и не важно успешного или нет.
Запуская предыдущий пример мы получим
Promise.allSettled
Этот метод вернет массив всех промиссов и успешных и нет. И вернется он в виде массива объектов.
Falling through promises (проваливание промисса)
Нельзя не упомянуть о таком явлении как “проваливание” промиса. Допусти, у нас есть какой-то промис, он резолвится далее мы используем метод then() чтобы обработать результат, но ничего не возвращаем, или выполняем другой промис, что же мы получим в итоге на следующем этапе?
const promise1 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Сообщение №1');
}, 500)
});
const promise2 = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Сообщение №2');
}, 500)
});
promise1
.then(promise2)
.then((data) => console.log(data)) // ???
На первый взгляд может показаться, что будет результат из второго промиса и мы увидим “сообщение №2”
или
promise1
.then(null)
.then((data) => console.log(data)) // ???
Тут можно предположить, что будет null?
Но на самом деле результат из первого промиса будет просто прокидываться далее, вследствие того, что ничего не возвращается из промежуточного then()
Для того, что исправить это поведение, нужно явно вернуть результат из метода then.
promise1
.then(() => promise2)
.then((data) => console.log(data)) // Сообщение №2
promise1
.then(() => null)
.then((data) => console.log(data)) // null















Top comments (0)