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

Promise

При стандартном выполнении JavaScript инструкции выполняются последовательно, одна за другой. То есть сначала выполняется первая инструкция, потом вторая и так далее. Однако что, если одна из этих операций выполняется продолжительное время. Например, она выполняет какую-то высоконагруженную работу, как обращение по сети или обращение к базе данных, что может занять неопределенное и иногда продолжительное время. В итоге при последовательном выполнении все последующие операции будут ожидать выполнения этой операции. Чтобы избежать подобной ситуации JavaScript предоставляет ряд инструментов, которые позволяют избежать подобного сценария, чтобы последующие операции могли выполняться, пока выполняется продолжительная операция. Одним из таким инструментов являются промисы (promise).

Промис (promise) — это объект, представляющий результат успешного или неудачного завершения асинхронной операции. Асинхронная операция, упрощенно говоря, это некоторое действие, выполняется независимо от окружающего ее кода, в котором она вызывается, не блокирует выполнение вызываемого кода.

Промис может находиться в одном из следующих состояний:

pending (состояние ожидания): начальное состояние, промис создан, но выполнение еще не завершено

fulfilled (успешно завершено): действие, которое представляет промис, успешно завершено

rejected (завершено с ошибкой): при выполнении действия, которое представляет промис, произошла ошибка

Для создания промиса применяется конструктор типа Promise :

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

Здесь функция просто выводит на консоль сообщение. Соответственно при выполнении этого кода мы увидим на консоли сообщение "Выполнение асинхронной операции" .

При создании промиса, когда его функция еще не начала выполняться, промис переходит в состояние "pending", то есть ожидает выполнения.

Как правило, функция, которая передается в конструктор Promise, принимает два параметра:

Оба этих параметра — resolve и reject также представляют функции. И каждая из этих функций принимает параметр любого типа.

Первый параметр — функция resolve вызывается в случае успешного выполнения. Мы можем в нее передать значение, которое мы можем получить в результате успешного выполнения.

Второй параметр — функция reject вызывается, если выполнение операции завершилось с ошибкой. Мы можем в нее передать значение, которое представит некоторую информацию об ошибке.

Успешное выполнение промиса

Итак, первый параметр функции в конструкторе Promise — функция resolve выполняется при успешном выполненим. В эту функцию обычно передается значение, которое представляет результат операции при успешном выполнении. Это значение может представлять любой объект. Например, передадим в эту функцию строку:

Функция resolve() вызывается в конце выполняемой операции после всех действий. При вызове этой функции промис переходит в состояние fulfilled (успешно выполнено).

При этом стоит отметить, что теоретически мы можем возвратить из функции результат, но практического смысла в этом не будет:

Данное возвращаемое значение мы не сможем передать во вне. И если действительно надо возвратить какой-то результат, то он передается в функцию resolve() .

Передача информации об ошибке

Второй параметр функции в конструкторе Promise — функция reject вызывается при возникновении ошибки. В эту функцию обычно передается некоторая информация об ошибке, которое может представлять любой объект. Например:

При вызове функции reject() промис переходит в состояние rejected (завершилось с ошибкой).

Объединение resolve и reject

Естественно мы можем определить логику, при которой в зависимости от условий будут выполняться обе функции:

В данном случае, если значени константы y равно 0, то сообщаем об ошибке, вызывая функцию reject() . Если не равно 0, то выполняем операцию деления и передаем результат в функцию resolve() .

Promise

Эта документация связана с понятием асинхронности в JavaScript. Зачем нужен асинхронный код и как он работает описано в обзорной статье «Асинхронность в JS».

Кратко

Промис (Promise) — специальный объект JavaScript, который используется для написания и обработки асинхронного кода.

Асинхронные функции возвращают объект Promise в качестве значения. Внутри промиса работает асинхронная операция, которая управляет его состоянием.

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

  • pending — стартовое состояние, операция стартовала
  • fulfilled — получен результат
  • rejected — ошибка

Поменять можно состояние только один раз: перейти из pending либо в fulfilled, либо в rejected :

Схема трёх состояний промиса и переход между ними

У промиса есть методы then и catch , которые позволяют выполнять код при изменении его состояния.

Как пишется

Создание

Промис создаётся с помощью конструктора.

В конструктор передаётся функция-исполнитель асинхронной операции (англ. executor). Она вызывается сразу после создания промиса. Задача этой функции — выполнить асинхронную операцию и перевести состояние промиса в fulfilled (успех) или rejected (ошибка).

Изменить состояние промиса можно, вызвав колбэки, переданные аргументами в функцию:

2 колбэка Promise функции

  • первый параметр (в примере кода назван resolve ) — колбэк для перевода промиса в состояние fulfilled , при его вызове аргументом передаётся результат операции.
  • второй параметр (в примере кода назван reject ) — колбэк для перевода промиса в состояние rejected , при его вызове аргументом передаётся информация об ошибке

Схема создания асинхронной функции с промисом

  1. Создать функцию, которая будет выполнять асинхронную операцию
  1. Вернуть из функции созданный промис
  1. Аргументом в конструктор передать анонимную функцию, которая выполняет асинхронную операцию и переводит промис в состояние успех/ошибка в зависимости от результата.

💡 Если асинхронная операция работает через колбэки, то её стоит обернуть в промис, чтобы писать более читаемый код.

Функция getData принимает два аргумента — колбэк при успехе и колбэк при ошибке. Завернём эту функцию в промис:

Теперь можно писать:

Использование

В работе мы чаще используем промисы, чем создаём. Использовать промис — значит выполнять код при изменении состояния промиса.

Существует три метода, которые позволяют реагировать на изменение промиса: then , catch и finally .

then

Детальное описание работы then описано в статье Promise. Метод then

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

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

  • асинхронная операция — запрос данных у сервера
  • код, который мы хотим выполнить после её завершения — отрисовка списка

Метод then принимает в качестве аргумента функцию-колбэк, которая выполняется сразу после того, как промис поменял состояние на fulfilled . Параметр колбэка содержит результат выполнения операции:

В коде выше, асинхронная функция fetch возвращает промис, к которому применяется метод then . При его выполнении в переменной movies будет ответ сервера.

catch

Детальное описание работы catch описано в статье Promise. Метод catch

Метод catch используют, чтобы выполнить код в случае ошибки при выполнении асинхронной операции.

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

  • асинхронная операция — запрос данных у сервера
  • код, который мы хотим выполнить при ошибке — экран обрыва соединения

Метод catch принимает в качестве аргумента функцию-колбэк, которая выполняется сразу после того, как промис поменял состояние на rejected . Параметр колбэка содержит экземпляр ошибки:

В коде выше, асинхронная функция fetch возвращает промис, к которому применяется метод catch . При его выполнении в переменной error будет экземпляр ошибки.

finally

Детальное описание работы finally описано в Promise. Метод finally

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

Самый частый сценарий использования finally — работа с индикаторами загрузки. Перед началом асинхронной операции разработчик включает индикатор загрузки. Индикатор нужно убрать вне зависимости от того, как завершилась операция. Если этого не сделать, то пользователь не сможет взаимодействовать с интерфейсом.

Метод finally принимает в качестве аргумента функцию-колбэк, которая выполняется сразу после того, как промис поменял состояние на rejected , или fulfilled :

Цепочки методов

Методы then , catch и finally часто объединяют в цепочки вызовов для того, чтобы обработать и успешный, и ошибочный сценарий:

В этом случае при успешном завершении операции мы выполним код из then , при ошибке — код из catch . Затем выполнится код из finally .

Цепочки методов — очень гибкий подход. Он позволяет создавать зависимые асинхронные операции.

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

Промисы делают решение такой задачи простым и читаемым. Мы можем начинать следующее асинхронное действие внутри колбэка метода then . Все, что возвращается из колбэка, оборачивается в промис, поэтому в цепочку можно добавить новый then :

Обработка ошибок в цепочках методов

Цепочки then при обработке промисов могут быть очень большими. В примере выше цепочка состоит из 4 then и одного catch . Как в этом случае отработает catch ?

☝️ catch обрабатывает ошибки от всех then между ним и предыдущим catch .

Один метод catch, поставленный в конце цепочки

В примере выше наш catch — последний, а предыдущего нет, поэтому он будет обрабатывать все ошибки

Несколько методов catch, поставленных в середине и конце цепочки

Если в цепочке несколько catch , то каждый ловит ошибки от then , находящихся выше.

Один метод catch, поставленный в середине цепочки

Возможен вариант, когда финального catch нет. Тогда ошибки от последних then не будут обрабатываться.

💔 Такой код — плохой. Если в одном из последних then произойдёт ошибка, то приложение перестанет работать.

Как понять

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

Промис устроен таким образом, что рычаги управления его состоянием остаются у асинхронной функции. После создания, промис находится в состоянии ожидания pending . Когда асинхронная операция завершается, функция переводит промис в состояние успеха fulfilled или ошибки rejected .

С помощью методов then , catch и finally мы можем реагировать на изменение состояния промиса и выполнять код.

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

На практике

Николай Лопин

🛠 Промис становится «разрешённым» или «завершённым», когда он переходит из состояние pending в fulfilled или rejected . Состояние завершённого промиса нельзя поменять.

🛠 Всегда завершай использование промиса методом catch . Если этого не сделать, то при ошибке весь JavaScript на странице перестанет работать. JavaScript падает тихо.

Знакомство с промисами в JavaScript

Java Script

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

Промисы служат для управления асинхронными операциями в JavaScript. Они достаточно легки в применении и могут избавить вас от многих неприятностей. Когда у вас имеется множество асинхронных операций, промисы могут избавить от лишней мороки с формированием огромного числа обратных вызовов, которые могут перерасти в кошмар и сделать код неуправляемым.

Проблема обратных вызовов

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

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

Как вы видите, функция loadScript передается параметром обратного вызова, который выполняется после загрузки скрипта. Все это работает прекрасно, но возникает один большой вопрос: “Что случится, если скрипт по какой-либо причине не загрузится?”. Со стороны разработчика будет наивным игнорировать такую возможность.

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

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

Именно такие ситуации и приводят к возникновению кошмара в обратных вызовах.

Краткое введение

Прежде чем приступить к рассмотрению промисов и принципов их работы, давайте взглянем, что на эту тему говорит MDN:

“Объект Promise представляет окончательное выполнение (или провал) асинхронной операции, а также ее итоговое значение.”

Как отмечено в MDN, промисы применяются для управления асинхронными операциями. Фактически же они позволяют не только легко работать с множественными операциями, но и более эффективно обрабатывать ошибки, что гораздо сложнее делать при помощи обратных вызовов и событий. В дополнение к этому они еще и повышают читаемость кода.

Промис может иметь одно из четырех состояний:

  1. fulfilled: выполненный;
  2. rejected: отклоненный;
  3. pending: не выполнен и не отклонен;
  4. settled: давно выполнен. Это не настоящее состояние, а фигура речи.

Промисы в коде

Как было отмечено ранее, они главным образом применяются для управления асинхронными операциями наподобие API вызовов или файловых операций. Все эти операции инициируются внутри функции executor .

Если асинхронная операция успешна, ожидаемый результат возвращается функцией resolve . Если же при выполнении возникла неожиданная ошибка, то результат будет возвращен функцией reject . Давайте взглянем на процесс создания промиса:

Становится ясно, что конструктор Promise принимает функцию, называемую executor . У этой функции есть два параметра: resolve и reject , которые также являются функциями.

Теперь давайте создадим простой пример промиса для лучшего понимания:

В этом примере мы пробуем запросить данные из API. Если API вернет HTTP код состояния 200, значит запрос оказался успешным. В противном случае он провалился.

Другими словами, если HTTP код состояния 200 , то промис resolved . В ином другом случае rejected.

Как же нам применять промис, который мы только что создали?

Когда была вызвана функция resolve или reject , промис считается завершенным (settled). С этого момента начинается выполнение следующего звена в цепочке. Обычно это then или catch . Имейте в виду, что одним executor может быть вызвана только одна функция resolve или reject . После их выполнения состояние промиса окончательно меняется на завершенное и дальнейшие вызовы resolve или reject будут проигнорированы.

Когда промис выполнен успешно, активируется обратный вызов then . Вы также можете создавать цепочки вызовов методов then . Если же промис был отклонен, активируется обратный вызов catch . finally вызывается независимо от успеха или провала выполнения.

All и Race

Последние две функции, которые мы еще не рассмотрели — это all и race . Они полезны, но их используют реже остальных.

Идеальным примером применения Promise.all может выступить одновременная множественная отправка AJAX запросов.

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

Выводом в консоли будет: “Output: Second!”. Обратите внимание, что промис request1 никогда не будет resolved .

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *