W3docs

Промисы JavaScript

Промисы JavaScript: состояния pending, fulfilled и rejected, функция-исполнитель с resolve и reject, методы then/catch/finally и очередь микрозадач.

Промисы в JavaScript — мощный инструмент для управления асинхронными операциями, позволяющий писать более чистый и надёжный код. В этой главе объясняется, что такое промис, три состояния, в которых он может находиться, как работает функция-исполнитель с resolve и reject, как получать результаты с помощью .then, .catch и .finally, и когда именно выполняются колбэки промисов (очередь микрозадач). Понимание этих основ необходимо перед переходом к цепочкам промисов, Promise API и async/await.

Введение в промисы JavaScript

Promise — это object, представляющий значение, которое может быть ещё недоступно, но будет получено в будущем. Вместо того чтобы передавать колбэк в асинхронную функцию и надеяться, что он будет вызван, вы немедленно получаете object промиса и прикрепляете к нему колбэки. Это позволяет избежать глубоко вложенных колбэков, нередко называемых «callback hell», и даёт единый последовательный способ обработки как успеха, так и неудачи.

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

Три состояния промиса

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

  • pending — начальное состояние; операция ещё не завершена.
  • fulfilled — операция завершилась успешно, и промис имеет значение.
  • rejected — операция завершилась неудачей, и промис имеет причину (обычно Error).

Промис в состоянии pending может перейти либо в fulfilled, либо в rejected. После этого он становится settled и не может изменить состояние снова. Эта однонаправленная и однократная смена состояния делает промисы предсказуемыми: колбэк .then, прикреплённый к уже выполненному промису, всё равно будет запущен, а промис никогда не может переключиться из fulfilled обратно в rejected.

                  ┌─────────────┐  resolve(value)   ┌─────────────┐
                  │   pending   │ ────────────────▶ │  fulfilled  │
   new Promise ─▶ │             │                   └─────────────┘
                  │             │  reject(reason)   ┌─────────────┐
                  └─────────────┘ ────────────────▶ │  rejected   │
                                                    └─────────────┘

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

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

  • Вызовите resolve(value), чтобы выполнить промис со значением value.
  • Вызовите reject(reason), чтобы отклонить промис с причиной reason.

До вызова одной из них промис остаётся в состоянии pending.


javascript— editable

Исполнитель получает resolve и reject, чтобы завершить промис после окончания асинхронной работы. Здесь промис переходит в состояние fulfilled через таймер в одну секунду:


javascript— editable

Примечание: Значение имеет только первый вызов resolve или reject. После завершения промиса любые последующие вызовы resolve или reject игнорируются. Кроме того, если исполнитель синхронно бросает ошибку, промис автоматически отклоняется с этой ошибкой.

Обработка результатов с помощью .then, .catch и .finally

После создания промиса его результат потребляется с помощью методов .then, .catch и .finally. Именно так остальной код реагирует на завершённый промис.

Метод then

Метод .then используется для планирования колбэка, который выполняется при выполнении промиса. Чтобы промис перешёл в состояние fulfilled, должен быть вызван метод resolve. Аргумент, переданный в метод resolve, станет итоговым значением промиса.


javascript— editable

В этом коде промис перейдёт в состояние fulfilled только после истечения таймаута в 1000 мс, когда метод resolve будет вызван со значением "Done!".

Функция в части then выполняется только после вызова метода resolve.

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

Метод .catch

Метод .catch используется для обработки промиса в случае его отклонения. Это происходит либо при броске ошибки в блоке функции промиса, либо при вызове метода reject.


javascript— editable

Более подробные паттерны обработки ошибок — отклонение против брошенных ошибок, повторный бросок и восстановление внутри цепочки — см. в Обработка ошибок с промисами.

Метод .finally

Метод .finally позволяет выполнить код после завершения промиса независимо от его результата. Он не получает аргументов (не знает, был ли промис fulfilled или rejected) и передаёт результат или ошибку без изменений, поэтому идеально подходит для очистки, например скрытия индикатора загрузки.


javascript— editable

Когда выполняются колбэки промисов? (Микрозадачи)

Распространённый сюрприз заключается в том, что колбэки .then, .catch и .finally никогда не выполняются синхронно, даже если промис уже завершён. Они планируются в очереди микрозадач, которую движок обрабатывает только после завершения текущего синхронного кода.

Это означает, что синхронный код всегда выполняется первым, а колбэки промисов — до таймеров (setTimeout), которые находятся в отдельной очереди макрозадач.


javascript— editable

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

Получение данных из API с помощью промисов

Этот пример показывает, как получить данные из удалённого API с помощью промисов.


javascript— editable

Цепочки промисов

Цепочки промисов — мощная возможность, позволяющая связывать несколько асинхронных операций последовательно. Каждый .then возвращает новый промис, и то, что вы возвращаете (return) из обработчика, становится значением выполнения этого нового промиса — именно поэтому в примере ниже значение передаётся через несколько шагов. Подробнее см. в JavaScript: промисы и цепочки.


javascript— editable

Использование async/await с промисами JavaScript

Применение async/await позволяет упростить обработку асинхронных операций, делая код чище и понятнее, сохраняя при этом всю мощь промисов JavaScript. Подробнее об этом вы узнаете в разделе JavaScript async/await, а пока — простой пример.


javascript— editable

Заключение

Владение промисами JavaScript необходимо любому разработчику, который хочет эффективно управлять асинхронными операциями. Запомните основную модель: промис находится в состоянии pending до тех пор, пока исполнитель не вызовет resolve или reject, после чего он навсегда становится settled; результат читается с помощью .then/.catch/.finally; а эти колбэки всегда выполняются как микрозадачи, после завершения текущего синхронного кода.

Что изучить дальше

Практика

Практика
Что такое промис в JavaScript?
Что такое промис в JavaScript?
Was this page helpful?