Что такое промисы в JavaScript
easy

Что такое промисы в JavaScript

Обещают что-то сделать и не блокируют код

В JavaScript промис — это объект, который представляет собой результат выполнения какого-либо кода, то есть в буквальном смысле обещание программы вернуть успех или ошибку. Промисы используют для управления асинхронными операциями — такими, которые не блокируют выполнение последующего кода, пока ожидают завершения. Это может быть запрос к серверу, чтение файлов или что-то ещё, результат чего не доступен сразу же. Разбираемся, что такое синхронность и асинхронность, как работают промисы, как их правильно обрабатывать и почему они упрощают жизнь разработчикам.

Асинхронность: краткое введение

Код может быть синхронный и асинхронный. По умолчанию JS-код выполняется последовательно, строка за строкой, то есть синхронно с основным потоком времени. Время течёт, код выполняется, сплошная синхронность.

Но иногда нужно, чтобы какое-то действие программа выполняла через определённое время. Например, получала данные с сервера по нажатию кнопки. Это может занять несколько секунд, и, чтобы интерфейс не замирал на это время, используется асинхронный запрос данных. Это значит, что программа продолжит работать, обрабатывая другие задачи, пока ожидается завершение длительной операции.

Один из способов работы с асинхронностью — использовать обратные вызовы (колбэки, callbacks). В этом случае одна функция передаётся в другую в виде аргумента и выполняется позже — после того как завершится какая-то асинхронная операция. Допустим, мы пришли в кафе с другом, заказали кофе и попросили друга сообщить нам, когда кофе будет готов. Друг в этом случае выполняет роль колбэка: он ждёт завершения события — приготовления кофе, а затем выполняет действие, сообщает нам об этом. Пока друг ждёт, мы можем заниматься другими делами.

Но с обратными вызовами иногда возникает проблема: они могут сделать код сложным и неподдерживаемым. Если в коде много вложенных обратных вызовов, то возникает то, что программисты называют адом обратных вызовов. 

Вот пример такой ситуации:

getData(function(a){
    getMoreData(a, function(b){
        getMoreData(b, function(c){
            getMoreData(c, function(d){
                getMoreData(d, function(e){
                    // Спустились на пятый уровень вложенности
                    console.log(e); // Только здесь используем полученные данные
                });
            });
        });
    });
});

Здесь пять функций получения данных. Каждый новый уровень вложенности добавляет сложности, и для выполнения каждого нового запроса данных нужно ждать завершения предыдущего. Поддерживать и рефакторить такой код сложно. Чтобы управлять асинхронным кодом было проще и удобнее, в JS придумали промисы (Promise), что с английского переводится как «обещание».

Как работает промис

Если продолжить аналогию с заказом кофе, то промис работает так: вы делаете заказ — запрос — и ожидаете, что кофе будет готов через некоторое время. Кассир даёт вам чек и тем самым обещает, что вы получите какой-то результат. Пока кофе готовится, вам необязательно стоять у стойки, вы можете сесть за стол и почитать статью в «Коде» :-)

В виде чека кассир дал вам «промис» — обещание, что через некоторое время вы что-то получите: чашку кофе или возврат денег за его отсутствие.

В JavaScript промис — это специальный объект, представляющий собой результат асинхронной операции. Результат может быть доступен сразу, позже или никогда.

Промис может находиться в трёх состояниях:

  • pending — ожидание, начальное состояние;
  • fulfilled — получен результат;
  • rejected — промис отклонён, возникла ошибка.

Операция начинается, и промис переходит в состояние pending. Он остаётся в этом состоянии, пока не будет либо выполнен (fulfilled), либо отклонён (rejected).

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

  • then()
  • catch()
  • finally()

Дальше мы напишем функцию с промисом: посмотрим, как он работает изнутри, и правильно обработаем его результаты.

Пишем свой промис

Мы создадим асинхронную функцию orderCoffee(), которая будет имитировать заказ кофе и с помощью промиса обрабатывать результат выполнения. Функция будет выводить в консоль сообщение «Заказ принят! Ваш кофе готовится». Затем через заданное время с помощью Math.random() функция определит результат выполнения. В зависимости от результата промис разрешится с сообщением «Ваш заказ готов!» либо отклонится с сообщением «Извините, у нас сломалась кофемашина».

Начинаем писать функцию:

function orderCoffee() {
 console.log("Заказ принят! Ваш кофе готовится.");

Далее создаём внутри функции промис. Это делается с помощью конструктора new Promise. В конструктор мы передаём функцию — исполнитель асинхронной операции, которая вызывается сразу после создания промиса. Задача этой функции — выполнить асинхронную операцию и перевести состояние промиса в fulfilled или rejected. Поэтому в сам промис мы передаём аргументы (resolve, reject).

Нам нужно, чтобы функция orderCoffee() возвращала объект промиса. Поэтому пишем так:

return new Promise((resolve, reject) => {

Мы указали, что наша функция должна вернуть (return) созданный объект промиса. Теперь внутри промиса пишем асинхронный код. Сначала имитируем асинхронную операцию с использованием setTimeout():

setTimeout(() => {

Затем с помощью Math.random() задаём 80% вероятности, что кофе будет успешно приготовлен, и выводим соответствующие сообщения для двух условий:

       if (Math.random() < 0.8) {
       resolve("Ваш заказ готов!");
     } else {
       reject("Извините, у нас сломалась кофемашина.");
     }

В конце указываем задержку на приготовление в 2 секунды и закрываем функцию:

   }, 2000);
 });
}

Полный код:

function orderCoffee() {
 console.log("Заказ принят! Ваш кофе готовится.");
 return new Promise((resolve, reject) => {
   setTimeout(() => {
       if (Math.random() < 0.8) {
       resolve("Ваш заказ готов!");
     } else {
       reject("Извините, у нас сломалась кофемашина.");
     }
   }, 2000); 
 });
}

Мы создали функцию orderCoffee() с промисом внутри. Теперь, если мы вызовем функцию, то получим от неё лишь первое сообщение, но дальше никаких результатов не увидим: 

Что такое промисы в JavaScript

Внутри функции есть промис, но мы не добавили к вызову обработчик промиса. Поэтому он выполняется в фоновом режиме, и мы не можем увидеть результаты выполнения. Если в консоли раскрыть промис, мы увидим, что он перешёл в состояние fulfilled.

Что такое промисы в JavaScript

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

Обрабатываем результат промиса

Когда асинхронная операция завершается, промис из состояния pending переходит в состояние успеха или ошибки.

Что такое промисы в JavaScript

Для обработки этих результатов используются методы then(), catch() и finally().

Метод then() может принимать два аргумента и обработать как успех, так и ошибку промиса. Но хорошей практикой считается задавать в методе then() действия только для успешного завершения.

Напишем обработчик успешного события промиса. Когда мы писали промис, то передали ему аргумент функции-исполнителя resolve с соответствующим сообщением «Ваш заказ готов!». Теперь передадим это сообщение в первый обработчик в цепочке then():

 .then((result) => {
   console.log(result);
 })

Для обработки состояния ошибки промиса используется метод catch(), который получает один аргумент, представляющий ошибку: 

 .catch((error) => {
   console.error(error);
 });

Мы написали два обработчика, теперь добавим их к вызову нашей функции orderCoffee():

orderCoffee()
 .then((result) => {
   console.log(result);
 })
 .catch((error) => {
   console.error(error);
 });

Вызовем функцию вместе с обработчиками:

Что такое промисы в JavaScript

Мы получаем первое сообщение, а затем ждём, пока выполняется асинхронная операция. Спустя 2 секунды в консоль выводится результат.

Есть ещё метод finally(), и его можно использовать для выполнения кода, который должен быть исполнен после завершения промиса независимо от его разрешения или отклонения. Метод finally() особенно полезен для выполнения заключительных действий в программе: очистки ресурсов, закрытия файловых потоков или скрытия индикаторов загрузки в пользовательском интерфейсе.

В нашем примере finally() мог бы выглядеть так:

orderCoffee()
  .then((result) => {
    console.log(result); 
  })
  .catch((error) => {
    console.error(error); 
  })
  .finally(() => {
    console.log("Актуальные акции ищите в нашем приложении!");
  });

Что такое промисы в JavaScript

Часто все три метода объединяются в цепочки методов. Это позволяет создавать сложные последовательности асинхронных операций, где каждая следующая зависит от результата предыдущей. И всё это без ада обратных вызовов.

Как используется в реальной разработке

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

Большинство новых асинхронных API создаются на промисах. Например, метод fetch — альтернатива AJAX — использует промисы. Метод делает асинхронные HTTP-запросы к серверам и возвращает промис, который затем используется для получения и обработки ответа от сервера.

Редактор:

Инна Долога

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Маша Климентьева

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию Получите ИТ-профессию
Вам может быть интересно
easy