DEV Community

VadimSibg
VadimSibg

Posted on

Promise по-простому

Promise - это специальный объект, который гарантирует нам выполнение какого-то кода, но потом, по какому-то условия через определенное время. Как правило он используется при обращении к серверу, когда мы отправляем запрос и хотим сделать что-то с данными, которые придут в ответе.
Ранее, до появления промисов в es6, в нативном виде использовался XHR запрос, который был очень громоздким и для обработки ответа от сервера приходилось писать множество callback функций (существует даже термин callback hell), что значительно усложняло написание и восприятие кода. Промисы же как раз позволяют избежать огромной вложенности колбеков благодаря цепочкам последовательных обработок, но обо всем по порядку.

Создание промиса

Чтобы создать промисы мы должны воспользоваться конструктором new Promise и передать в него функцию, которая имеет два аргумента resolve и reject (это функции колбеки, они уже созданы в JS, их не нужно создавать или описывать, лишь только использовать) и непосредственно само тело функции, которое нам и нужно описать.

const myPromise = new Promise((resolve, reject) => {
   // Ваш код здесь
})

Enter fullscreen mode Exit fullscreen mode

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);

Enter fullscreen mode Exit fullscreen mode

В данном случае задержка от сервера заменена обычным setTimeout, в котором идет проверка есть пользователю 18 лет или нет, после чего, спустя 1 секунду, будет выбран либо успешный, либо неудачный сценарий выполнения промиса.
Если мы выведем наш промис в консоль и быстро раскроем его значение (до того как он будет разрешен), то увидим следующую картину:

Image description

Наш промис изначально находится в режиме ожидания, у него нет никакого результата. Однако, если мы повторном выведем его в консоль, спустя 1 секунду, то увидим следующее:

Image description

Наш промис разрешился удачно, результатом будет значение, переданное в функцию resolve(). Тоже самое мы можем увидеть, если после загрузки страницы раскроем объект промиса не сразу, а через 1 секунду:

Image description

Мы увидим актуальное значение на момент раскрытия объекта в инструментах разработчика.

Чтобы увидеть неудачное разрешение промиса, заменим возраст на число меньше 18 и посмотрим на результат вывода в консоль.

Image description

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);
})

Enter fullscreen mode Exit fullscreen mode

*then *
Данный метод обрабатывает результат промиса и имеет 2 аргумента - это функции: первая выполняется, когда промис разрешается успешно, вторая, обрабатывает неуспешно разрешенный промис и является необязательной.

myPromise
.then((success) => console.log(success), (error) => console.error(error))

Enter fullscreen mode Exit fullscreen mode

Если мы оставим id равным 1, то получим результат работы функции success

Image description

Если же сменим id на 2, то получим результат работы функции error

Image description

Но на практике второй аргумент практически не используется, для того чтобы перехватить ошибки существует следующий метод.

catch
Данный метод принимает всего один аргумент - результат ошибки в ходе выполнения промиса. Давайте перепишем наш пример, используя данный метод.

myPromise
.then((success) => console.log(success))
.catch((error) => console.error(error));

Enter fullscreen mode Exit fullscreen mode

В некоторых источниках указано, что catch - это аналог второго аргумента метода then, но на самом деле это не так. Второй аргумент из метода then перехватит ошибку из промиса тут все верно, но зато не перехватит ошибку, которая может возникнуть внутри обработчика, который стоит в первом аргумента, а ведь ошибка может возникнуть не только в процессе выполнения промиса, но также и в процессе обработки успешно завершенного промиса. Зато catch будет отлавливать любую ошибку, на любом этапе, а также после catch могут идти следующий обработчики, которые обработают код внутри блока catch. То есть catch не финальный обработчик, дальше может что-то еще идти и именно из всех этих перечисленных нюансов его использование методов then + catch более логично, нежели использование метода then с двумя функциями-обработчиками.

finally
Данный метод не похож на предыдущие: он ничего не принимает и ничего не возвращает. Он просто выполняет код внутри себя и обычно располагается в конце цепочки. Но если же после него расположены еще другие методы .then или .catch, то он просто пропустит значение сквозь себя. Как правило он используется для остановки лоадера, логирования информации и тд

Promise API

Для работы с промисами также есть специальные методы, которые позволяют работать с группой промисов и обрабатывать их более гибко.

Promise.all
Вначале немного про сам метод. Метод Promise.all принимает массив промисов (может принимать любой перебираемый объект, но обычно используется массив) и возвращает новый промис.

Новый промис завершится, когда завершится весь переданный список промисов, и его результатом будет массив их результатов.

Например у нас есть 3 промиса:

Image description

Ответ придет тогда, когда завершится последний промис, а ответом будет массив результатов промисов в том же порядке и не важно какой из промисов оказался быстрее.

Image description

Вот только если в каком-то промисе будет ошибка, то на выходе мы не получим массива с результатами, а получим просто ошибку.

Image description

Image description

Promise.race
Этот метод очень похож на Promise.all, но дожидается просто первого промиса и не важно успешного или нет.
Запуская предыдущий пример мы получим

Image description
Image description

Promise.allSettled
Этот метод вернет массив всех промиссов и успешных и нет. И вернется он в виде массива объектов.

Image description
Image description
Image description

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)) // ???

Enter fullscreen mode Exit fullscreen mode

На первый взгляд может показаться, что будет результат из второго промиса и мы увидим “сообщение №2”
или

promise1
   .then(null)
   .then((data) => console.log(data)) // ???

Enter fullscreen mode Exit fullscreen mode

Тут можно предположить, что будет null?

Но на самом деле результат из первого промиса будет просто прокидываться далее, вследствие того, что ничего не возвращается из промежуточного then()

Для того, что исправить это поведение, нужно явно вернуть результат из метода then.

promise1
   .then(() => promise2)
   .then((data) => console.log(data)) // Сообщение №2

promise1
   .then(() => null)
   .then((data) => console.log(data)) // null

Enter fullscreen mode Exit fullscreen mode

Top comments (0)