Promise по-простому
Published: 2022-08-01 08:49:58

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 секунду, будет выбран либо успешный, либо неудачный сценарий выполнения промиса. 
Если мы выведем наш промис в консоль и быстро раскроем его значение (до того как он будет разрешен), то увидим следующую картину:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vl9k2swkagpiosuxthsb.png)

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7aynwmfw4pd2kc16d7it.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oc3lwixll94o9w63sslu.png)
 
Мы увидим актуальное значение на момент раскрытия объекта в инструментах разработчика.

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pnlnjslisrlnj954ylm1.png)

## 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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r23zkwt0vh6cd83rf0ja.png)

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


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ynst6qofxxpmcnzt0whv.png)

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

**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 промиса:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/23w5oyxxhygnw33w0ijg.png)

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rttwqw390qwkzess36z8.png)
 
Вот только если в каком-то промисе будет ошибка, то на выходе мы не получим массива с результатами, а получим просто ошибку.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fhw0m1cj04y9yrk1royz.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i8ag7t7omoxxy6chlftd.png)
 
 
**Promise.race**
Этот метод очень похож на Promise.all, но дожидается просто первого промиса и не важно успешного или нет.
Запуская предыдущий пример мы получим

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ax150q3o8lsu6ympdl34.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hsenlwmdonm3285zxz4a.png)
  
**Promise.allSettled**
Этот метод вернет массив всех промиссов и успешных и нет. И вернется он в виде массива объектов.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g1f3xeo2ytpe3cj6e349.png)
 ![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4od9897sooxtfwxksyf3.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rq3vbnv95ix0d6w5k7tu.png)

**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

```