Promise
Как уйти за значением выражения и вернуться, когда оно будет доступно.
Время чтения: 11 мин
Открыть/закрыть навигацию по статье
- Кратко
- Как пишется
- Как понять
- Методы
- Цепочки методов
- Обработка ошибок в цепочках методов
- Николай Лопин советует
- Реализуйте полифил для Promise.all
Контрибьюторы:
Обновлено 8 июня 2023
Эта документация связана с понятием асинхронности в JavaScript. Зачем нужен асинхронный код и как он работает описано в обзорной статье «Асинхронность в JS».
Кратко
Скопировать ссылку «Кратко» Скопировано
Промис (Promise) — специальный объект JavaScript, который используется для написания и обработки асинхронного кода.
Асинхронные функции возвращают объект Promise в качестве значения. Внутри промиса хранится результат вычисления, которое может быть уже выполнено или выполнится в будущем.
Промис может находиться в одном из трёх состояний:
- pending — стартовое состояние, операция стартовала;
- fulfilled — получен результат;
- rejected — ошибка.
Поменять состояние можно только один раз: перейти из pending либо в fulfilled , либо в rejected :
У промиса есть методы then ( ) и catch ( ) , которые позволяют использовать результат вычисления внутри промиса.
Как пишется
Скопировать ссылку «Как пишется» Скопировано
Промис создаётся с помощью конструктора.
В конструктор передаётся функция-исполнитель асинхронной операции (англ. executor). Она вызывается сразу после создания промиса. Задача этой функции — выполнить асинхронную операцию и перевести состояние промиса в fulfilled (успех) или rejected (ошибка).
Изменить состояние промиса можно, вызвав колбэки, переданные аргументами в функцию:

const promise = new Promise(function (resolve, reject) const data = getData() // делаем асинхронную операцию: запрос в БД, API, etc. resolve(data) // переводим промис в состояние fulfilled. Результатом выполнения будет объект data>) const errorPromise = new Promise(function (resolve, reject) reject(new Error('ошибка')) // переводим промис в состояние rejected. Результатом выполнения будет объект Error>)const promise = new Promise(function (resolve, reject) const data = getData() // делаем асинхронную операцию: запрос в БД, API, etc. resolve(data) // переводим промис в состояние fulfilled. Результатом выполнения будет объект data >) const errorPromise = new Promise(function (resolve, reject) reject(new Error('ошибка')) // переводим промис в состояние rejected. Результатом выполнения будет объект Error >)- первый параметр (в примере кода назван resolve ) — колбэк для перевода промиса в состояние fulfilled , при его вызове аргументом передаётся результат операции;
- второй параметр (в примере кода назван reject ) — колбэк для перевода промиса в состояние rejected , при его вызове аргументом передаётся информация об ошибке.
Как понять
Скопировать ссылку «Как понять» Скопировано
Промис решает задачу выполнения кода, который зависит от результата асинхронной операции.
Промис устроен таким образом, что рычаги управления его состоянием остаются у асинхронной функции. После создания, промис находится в состоянии ожидания pending . Когда асинхронная операция завершается, функция переводит промис в состояние успеха fulfilled или ошибки rejected .
С помощью методов then ( ) , catch ( ) и finally ( ) мы можем реагировать на изменение состояния промиса и использовать результат его выполнения.
Пример ниже показывает состояние промиса, который создаётся при нажатии на кнопку купить. Промис случайным образом завершается успехом или ошибкой:
Методы
Скопировать ссылку «Методы» Скопировано
В работе мы чаще используем промисы, чем создаём. Использовать промис — значит выполнять код при изменении состояния промиса.
Существует три метода, которые позволяют работать с результатом выполнения вычисления внутри промиса:
- then ( )
- catch ( )
- finally ( )
then
Подробнее о работе then ( ) читайте в статье Promise. Метод then ( ) .
Метод then ( ) используют, чтобы выполнить код после успешного выполнения асинхронной операции.
Например, мы запросили у сервера список фильмов и хотим отобразить их на экране, когда сервер получит результат. В этом случае:
- асинхронная операция — запрос данных у сервера;
- код, который мы хотим выполнить после её завершения, — отрисовка списка.
Метод then ( ) принимает в качестве аргумента две функции-колбэка. Если промис в состоянии fulfilled то выполнится первая функция. Если в состоянии rejected — вторая. Хорошей практикой считается не использовать второй аргумент метода then ( ) и обрабатывать ошибки при помощи метода catch ( ) .
fetch(`https://swapi.dev/api/films/$/`).then(function (movies) renderList(movies)>)fetch(`https://swapi.dev/api/films/$id>/`).then(function (movies) renderList(movies) >)В коде выше, асинхронная функция fetch ( ) возвращает промис, к которому применяется метод then . При его выполнении в переменной movies будет ответ сервера.
catch
Подробнее о работе catch ( ) читайте в статье Promise. Метод catch ( ) .
Метод catch ( ) используют, чтобы выполнить код в случае ошибки при выполнении асинхронной операции.
Например, мы запросили у сервера список фильмов и хотим показать экран обрыва соединения, если произошла ошибка. В этом случае:
- асинхронная операция — запрос данных у сервера;
- код, который мы хотим выполнить при ошибке — экран обрыва соединения.
Метод catch ( ) принимает в качестве аргумента функцию-колбэк, которая выполняется сразу после того, как промис поменял состояние на rejected . Параметр колбэка содержит экземпляр ошибки:
fetch(`https://swapi.dev/api/films/$/`).catch(function (error) renderErrorMessage(error)>)fetch(`https://swapi.dev/api/films/$id>/`).catch(function (error) renderErrorMessage(error) >)В коде выше, асинхронная функция fetch ( ) возвращает промис, к которому применяется метод catch ( ) . При его выполнении в переменной error будет экземпляр ошибки.
finally
Подробнее о работе finally ( ) читайте в статье Promise. Метод finally ( ) .
Метод finally ( ) используют, чтобы выполнить код при завершении асинхронной операции. Он будет выполнен вне зависимости от того, была ли операция успешной или завершилась ошибкой.
Самый частый сценарий использования finally ( ) — работа с индикаторами загрузки. Перед началом асинхронной операции разработчик включает индикатор загрузки. Индикатор нужно убрать вне зависимости от того, как завершилась операция. Если этого не сделать, то пользователь не сможет взаимодействовать с интерфейсом.
Метод finally ( ) принимает в качестве аргумента функцию-колбэк, которая выполняется сразу после того, как промис поменял состояние на rejected или fulfilled :
let isLoading = truefetch(`https://swapi.dev/api/films/$/`).finally(function () isLoading = false>)let isLoading = true fetch(`https://swapi.dev/api/films/$id>/`).finally(function () isLoading = false >)Цепочки методов
Скопировать ссылку «Цепочки методов» Скопировано
Методы then ( ) , catch ( ) и finally ( ) часто объединяют в цепочки вызовов, чтобы обработать и успешный, и ошибочный сценарии:
let isLoading = true fetch(`https://swapi.dev/api/films/$/`) .then(function (movies) renderList(movies) >) .catch(function (err) renderErrorMessage(err) >) .finally(function () isLoading = false >)let isLoading = true fetch(`https://swapi.dev/api/films/$id>/`) .then(function (movies) renderList(movies) >) .catch(function (err) renderErrorMessage(err) >) .finally(function () isLoading = false >)В этом случае при успешном завершении операции мы выполним код из then ( ) , при ошибке — код из catch ( ) . Затем выполнится код из finally ( ) .
Цепочки методов — очень гибкий подход. Он позволяет создавать зависимые асинхронные операции.
Например, нужно отобразить информацию о фильме и главном герое. Мы не знаем, кто главный герой, не получив эту информацию из данных о фильме. Таким образом, запрос данных о герое зависит от результата запроса данных о фильме.
Промисы делают решение простым и читаемым. Мы можем начинать следующее асинхронное действие внутри колбэка метода then ( ) . Все, что возвращается из колбэка, оборачивается в промис, поэтому в цепочку можно добавить новый then ( ) :
fetch(`https://swapi.dev/api/films/$/`) .then(function (response) // этот then сработает, когда разрешится промис с запросом данных о фильме return response.json() // нужно распарсить ответ сервера, это асинхронная операция >) .then(function (movie) // этот then сработает, когда данные о фильме распарсятся const characterUrl = movie.characters[0] return fetch(characterUrl) // вызов fetch вернет промис, возвращаем его из колбэка, чтобы продолжить цепочку >) .then(function (response) // этот then сработает, когда разрешится промис с результатами запроса персонажа return response.json() >) .then(function (character) renderCharacterProfile(character) >) .catch(function (err) // catch сработает, когда любая из операций выше завершится ошибкой renderErrorMessage(err) >)fetch(`https://swapi.dev/api/films/$id>/`) .then(function (response) // этот then сработает, когда разрешится промис с запросом данных о фильме return response.json() // нужно распарсить ответ сервера, это асинхронная операция >) .then(function (movie) // этот then сработает, когда данные о фильме распарсятся const characterUrl = movie.characters[0] return fetch(characterUrl) // вызов fetch вернет промис, возвращаем его из колбэка, чтобы продолжить цепочку >) .then(function (response) // этот then сработает, когда разрешится промис с результатами запроса персонажа return response.json() >) .then(function (character) renderCharacterProfile(character) >) .catch(function (err) // catch сработает, когда любая из операций выше завершится ошибкой renderErrorMessage(err) >)Обработка ошибок в цепочках методов
Скопировать ссылку «Обработка ошибок в цепочках методов» Скопировано
Цепочки then ( ) при обработке промисов могут быть очень большими. В примере выше цепочка состоит из четырёх then ( ) и одного catch ( ) . Как в этом случае отработает catch ( ) ?
☝️ catch ( ) обрабатывает ошибки от всех then ( ) между ним и предыдущим catch ( ) .

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

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

Возможен вариант, когда финального catch ( ) нет. Тогда ошибки от последних then ( ) не будут обрабатываться.
⚠️ Такой код — плохой. Если в одном из последних then ( ) произойдёт ошибка, то вся дальнейшая цепочка не отработает, при этом, из-за асинхронной природы промиса, прочий код вне промиса продолжит работать и приложение не упадёт.
Другие полезные методы промисов
Скопировать ссылку «Другие полезные методы промисов» Скопировано
Иногда вам нужно обернуть уже известный результат вычисления в промис. Для этого вы можете использовать метод Promise . resolve ( ) :
const happyDog = Promise.resolve('')happyDog.then(function (dog) console.log(dog) // >)const happyDog = Promise.resolve('') happyDog.then(function (dog) console.log(dog) // >)Кроме этого есть метод Promise . reject ( ) , он используется реже. Обратите внимание, что результатом выполнения sad Dog . catch ( ) будет промис в статусе fulfilled
const sadDog = Promise.reject('')sadDog.catch(function (dog) console.log(dog) // >)const sadDog = Promise.reject('') sadDog.catch(function (dog) console.log(dog) // >)Как создать асинхронную функцию с промисом
Скопировать ссылку «Как создать асинхронную функцию с промисом» Скопировано
- Создать функцию, которая будет выполнять асинхронную операцию:
function earnAllMoney() <>function earnAllMoney() >- Вернуть из функции свежесозданный промис:
function earnAllMoney() return new Promise(function (resolve, reject) < /* . */ >)>function earnAllMoney() return new Promise(function (resolve, reject) /* . */ >) >- Аргументом в конструктор передать функцию, которая выполняет асинхронную операцию и переводит промис в состояние «успех» или «ошибка» в зависимости от результата:
function earnAllMoney() return new Promise(function (resolve, reject) const result = tryEarnAllMoney() // асинхронная операция if (result.ok) resolve(result) // успех → переводим промис в fulfilled и передаём результат > else reject(new Error(result)) // ошибка → переводим промис в rejected > >)>function earnAllMoney() return new Promise(function (resolve, reject) const result = tryEarnAllMoney() // асинхронная операция if (result.ok) resolve(result) // успех → переводим промис в fulfilled и передаём результат > else reject(new Error(result)) // ошибка → переводим промис в rejected > >) >Если асинхронная операция работает через колбэки, то её стоит обернуть в промис, чтобы писать более читаемый код.
Рассмотрим пример с функцией get Data , которая принимает два колбэка: первый вызывается при успехе, второй — при ошибке. Код этой функции может выглядеть так:
function getData(onSuccess, onError) setTimeout(function () const result = Math.random() if (result > 0.5) onSuccess(result) > else onError(new Error('Что-то пошло не так')) > >, 1000)>function getData(onSuccess, onError) setTimeout(function () const result = Math.random() if (result > 0.5) onSuccess(result) > else onError(new Error('Что-то пошло не так')) > >, 1000) >Завернём её в промис:
function promisifiedGetData() return new Promise(function (resolve, reject) const result = getData( function (result) resolve(result) >, function (error) reject(error) > ) >)>function promisifiedGetData() return new Promise(function (resolve, reject) const result = getData( function (result) resolve(result) >, function (error) reject(error) > ) >) >Теперь можно использовать методы then ( ) и catch ( ) :
promisifiedGetData() .then(function () console.log('success') >) .catch(function (err) console.error(err.message) >)promisifiedGetData() .then(function () console.log('success') >) .catch(function (err) console.error(err.message) >)Промисы схлопываются
Скопировать ссылку «Промисы схлопываются» Скопировано
Интересная и удобная особенность промисов – если вложить один промис в другой они схлопнутся в один. Например:
const promise = Promise.resolve(Promise.resolve(Promise.resolve('')))// Promise : ''>promise.then(console.log)//const promise = Promise.resolve(Promise.resolve(Promise.resolve(''))) // Promise : ''> promise.then(console.log) //На практике
Скопировать ссылку «На практике» Скопировано
Николай Лопин советует
Скопировать ссылку «Николай Лопин советует» Скопировано
Промис становится «разрешённым» или «завершённым», когда он переходит из состояние pending в fulfilled или rejected . Состояние завершённого промиса нельзя поменять.
const promise = new Promise(function (resolve, reject) resolve() // в этот момент промис переходит в состояние fulfilled reject() // этот вызов игнорируется, потому что промис разрешился>)const promise = new Promise(function (resolve, reject) resolve() // в этот момент промис переходит в состояние fulfilled reject() // этот вызов игнорируется, потому что промис разрешился >)Всегда завершайте использование промиса методом catch ( ) . Если этого не сделать, то следующие промисы в цепочке перестанут работать, и такую ошибку получится поймать только через специальный обработчик – unhandledrejection .
Время от времени нужно выполнить несколько асинхронных функций и дождаться, пока все выполнятся или одна из них завершится ошибкой. Для этого существует статический метод Promise . all ( ) (он возвращает промис).
Если нужно дождаться пока несколько асинхронных функций завершатся (без разницы, успешно или ошибкой), используйте метод Promise . all Settled ( ) (вернёт промис).
На собеседовании
Скопировать ссылку «На собеседовании» Скопировано
Что такое promise js
Промис (promise) — это объект, представляющий результат успешного или неудачного завершения асинхронной операции. Асинхронная операция, упрощенно говоря, это некоторое действие, выполняется независимо от окружающего ее кода, в котором она вызывается, не блокирует выполнение вызываемого кода.
Промис может находиться в одном из следующих состояний:
- pending (состояние ожидания): начальное состояние, промис создан, но выполнение еще не завершено
- fulfilled (успешно завершено): действие, которое представляет промис, успешно завершено
- rejected (завершено с ошибкой): при выполнении действия, которое представляет промис, произошла ошибка
Для создания промиса применяется конструктор типа Promise :
new Promise(executor)
В качестве параметра конструктор принимает функцию, которая выполняется при создании промиса. Обычно эта функция представляет асинхронные операции, которые занимают продолжительное время. Например, определим простейший промис:
const myPromise = new Promise(function()< console.log("Выполнение асинхронной операции"); >);Здесь функция просто выводит на консоль сообщение. Соответственно при выполнении этого кода мы увидим на консоли сообщение «Выполнение асинхронной операции» .
При создании промиса, когда его функция еще не начала выполняться, промис переходит в состояние «pending», то есть ожидает выполнения.
Для эмуляции асинхронности определим несколько промисов:
const myPromise3000 = new Promise(function()< console.log("[myPromise3000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise3000] Завершение асинхронной операции"), 3000); >); const myPromise1000 = new Promise(function()< console.log("[myPromise1000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise1000] Завершение асинхронной операции"), 1000); >); const myPromise2000 = new Promise(function()< console.log("[myPromise2000] Выполнение асинхронной операции"); setTimeout(()=>console.log("[myPromise2000] Завершение асинхронной операции"), 2000); >);Здесь определены три однотипных промиса. Чтобы каждый из них не выполнялся сразу, они используют функцию setTimeout с задержкой в несколько секунд. Для разных промисов длительность задержки различается. И в данном случае мы получим следующий консольный вывод:
[myPromise3000] Выполнение асинхронной операции [myPromise1000] Выполнение асинхронной операции [myPromise2000] Выполнение асинхронной операции [myPromise1000] Завершение асинхронной операции [myPromise2000] Завершение асинхронной операции [myPromise3000] Завершение асинхронной операции
Здесь мы видим, что первым начал выполняться промис myPromise3000, однако он же завершился последним, так как для него установлено наибольшее время задержки — 3 секунды. Однако его задержка не помешала выполнению остальных промисов.
resolve и reject
Как правило, функция, которая передается в конструктор Promise, принимает два параметра:
const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); >);Оба этих параметра — resolve и reject также представляют функции. И каждая из этих функций принимает параметр любого типа.
Первый параметр — функция resolve вызывается в случае успешного выполнения. Мы можем в нее передать значение, которое мы можем получить в результате успешного выполнения.
Второй параметр — функция reject вызывается, если выполнение операции завершилось с ошибкой. Мы можем в нее передать значение, которое представит некоторую информацию об ошибке.
Успешное выполнение промиса
Итак, первый параметр функции в конструкторе Promise — функция resolve выполняется при успешном выполненим. В эту функцию обычно передается значение, которое представляет результат операции при успешном выполнении. Это значение может представлять любой объект. Например, передадим в эту функцию строку:
const myPromise = new Promise(function(resolve)< console.log("Выполнение асинхронной операции"); resolve("Привет мир!"); >);Функция resolve() вызывается в конце выполняемой операции после всех действий. При вызове этой функции промис переходит в состояние fulfilled (успешно выполнено).
При этом стоит отметить, что теоретически мы можем возвратить из функции результат, но практического смысла в этом не будет:
const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); return "Привет мир!"; >);Данное возвращаемое значение мы не сможем передать во вне. И если действительно надо возвратить какой-то результат, то он передается в функцию resolve() .
Передача информации об ошибке
Второй параметр функции в конструкторе Promise — функция reject вызывается при возникновении ошибки. В эту функцию обычно передается некоторая информация об ошибке, которое может представлять любой объект. Например:
const myPromise = new Promise(function(resolve, reject)< console.log("Выполнение асинхронной операции"); reject("Переданы некорректные данные"); >);При вызове функции reject() промис переходит в состояние rejected (завершилось с ошибкой).
Объединение resolve и reject
Естественно мы можем определить логику, при которой в зависимости от условий будут выполняться обе функции:
const x = 4; const y = 0; const myPromise = new Promise(function(resolve, reject) < if(y === 0) < reject("Переданы некорректные данные"); >else < const z = x / y; resolve(z); >>);В данном случае, если значени константы y равно 0, то сообщаем об ошибке, вызывая функцию reject() . Если не равно 0, то выполняем операцию деления и передаем результат в функцию resolve() .
Promise
The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value.
To learn about the way promises work and how you can use them, we advise you to read Using promises first.
Description
A Promise is a proxy for a value not necessarily known when the promise is created. It allows you to associate handlers with an asynchronous action’s eventual success value or failure reason. This lets asynchronous methods return values like synchronous methods: instead of immediately returning the final value, the asynchronous method returns a promise to supply the value at some point in the future.
A Promise is in one of these states:
- pending: initial state, neither fulfilled nor rejected.
- fulfilled: meaning that the operation was completed successfully.
- rejected: meaning that the operation failed.
The eventual state of a pending promise can either be fulfilled with a value or rejected with a reason (error). When either of these options occur, the associated handlers queued up by a promise’s then method are called. If the promise has already been fulfilled or rejected when a corresponding handler is attached, the handler will be called, so there is no race condition between an asynchronous operation completing and its handlers being attached.
A promise is said to be settled if it is either fulfilled or rejected, but not pending.

You will also hear the term resolved used with promises — this means that the promise is settled or «locked-in» to match the eventual state of another promise, and further resolving or rejecting it has no effect. The States and fates document from the original Promise proposal contains more details about promise terminology. Colloquially, «resolved» promises are often equivalent to «fulfilled» promises, but as illustrated in «States and fates», resolved promises can be pending or rejected as well. For example:
new Promise((resolveOuter) => resolveOuter( new Promise((resolveInner) => setTimeout(resolveInner, 1000); >), ); >);
This promise is already resolved at the time when it’s created (because the resolveOuter is called synchronously), but it is resolved with another promise, and therefore won’t be fulfilled until 1 second later, when the inner promise fulfills. In practice, the «resolution» is often done behind the scenes and not observable, and only its fulfillment or rejection are.
Note: Several other languages have mechanisms for lazy evaluation and deferring a computation, which they also call «promises», e.g. Scheme. Promises in JavaScript represent processes that are already happening, which can be chained with callback functions. If you are looking to lazily evaluate an expression, consider using a function with no arguments e.g. f = () => expression to create the lazily-evaluated expression, and f() to evaluate the expression immediately.
Chained Promises
The methods Promise.prototype.then() , Promise.prototype.catch() , and Promise.prototype.finally() are used to associate further action with a promise that becomes settled. As these methods return promises, they can be chained.
The .then() method takes up to two arguments; the first argument is a callback function for the fulfilled case of the promise, and the second argument is a callback function for the rejected case. Each .then() returns a newly generated promise object, which can optionally be used for chaining; for example:
const myPromise = new Promise((resolve, reject) => setTimeout(() => resolve("foo"); >, 300); >); myPromise .then(handleFulfilledA, handleRejectedA) .then(handleFulfilledB, handleRejectedB) .then(handleFulfilledC, handleRejectedC);
Processing continues to the next link of the chain even when a .then() lacks a callback function. Therefore, a chain can safely omit every rejection callback function until the final .catch() .
Handling a rejected promise in each .then() has consequences further down the promise chain. Sometimes there is no choice, because an error must be handled immediately. In such cases we must throw an error of some type to maintain error state down the chain. On the other hand, in the absence of an immediate need, it is simpler to leave out error handling until a final .catch() statement. A .catch() is really just a .then() without a slot for a callback function for the case when the promise is fulfilled.
.then(handleFulfilledA) .then(handleFulfilledB) .then(handleFulfilledC) .catch(handleRejectedAny);
Using arrow functions for the callback functions, implementation of the promise chain might look something like this:
.then((value) => `$value> and bar`) .then((value) => `$value> and bar again`) .then((value) => `$value> and again`) .then((value) => `$value> and again`) .then((value) => console.log(value); >) .catch((err) => console.error(err); >);
Note: For faster execution, all synchronous actions should preferably be done within one handler, otherwise it would take several ticks to execute all handlers in sequence.
The termination condition of a promise determines the «settled» state of the next promise in the chain. A «fulfilled» state indicates a successful completion of the promise, while a «rejected» state indicates a lack of success. The return value of each fulfilled promise in the chain is passed along to the next .then() , while the reason for rejection is passed along to the next rejection-handler function in the chain.
The promises of a chain are nested in one another, but get popped like the top of a stack. The first promise in the chain is most deeply nested and is the first to pop.
(promise D, (promise C, (promise B, (promise A) ) ) )
When a nextValue is a promise, the effect is a dynamic replacement. The return causes a promise to be popped, but the nextValue promise is pushed into its place. For the nesting shown above, suppose the .then() associated with «promise B» returns a nextValue of «promise X». The resulting nesting would look like this:
(promise D, (promise C, (promise X) ) )
A promise can participate in more than one nesting. For the following code, the transition of promiseA into a «settled» state will cause both instances of .then() to be invoked.
const promiseA = new Promise(myExecutorFunc); const promiseB = promiseA.then(handleFulfilled1, handleRejected1); const promiseC = promiseA.then(handleFulfilled2, handleRejected2);
An action can be assigned to an already «settled» promise. In that case, the action (if appropriate) will be performed at the first asynchronous opportunity. Note that promises are guaranteed to be asynchronous. Therefore, an action for an already «settled» promise will occur only after the stack has cleared and a clock-tick has passed. The effect is much like that of setTimeout(action, 0) .
const promiseA = new Promise((resolve, reject) => resolve(777); >); // At this point, "promiseA" is already settled. promiseA.then((val) => console.log("asynchronous logging has val:", val)); console.log("immediate logging"); // produces output in this order: // immediate logging // asynchronous logging has val: 777
Thenables
The JavaScript ecosystem had made multiple Promise implementations long before it became part of the language. Despite being represented differently internally, at the minimum, all Promise-like objects implement the Thenable interface. A thenable implements the .then() method, which is called with two callbacks: one for when the promise is fulfilled, one for when it’s rejected. Promises are thenables as well.
To interoperate with the existing Promise implementations, the language allows using thenables in place of promises. For example, Promise.resolve will not only resolve promises, but also trace thenables.
const aThenable = then(onFulfilled, onRejected) onFulfilled( // The thenable is fulfilled with another thenable then(onFulfilled, onRejected) onFulfilled(42); >, >); >, >; Promise.resolve(aThenable); // A promise fulfilled with 42
Promise concurrency
The Promise class offers four static methods to facilitate async task concurrency:
Fulfills when all of the promises fulfill; rejects when any of the promises rejects.
Fulfills when all promises settle.
Fulfills when any of the promises fulfills; rejects when all of the promises reject.
Settles when any of the promises settles. In other words, fulfills when any of the promises fulfills; rejects when any of the promises rejects.
All these methods take an iterable of promises (thenables, to be exact) and return a new promise. They all support subclassing, which means they can be called on subclasses of Promise , and the result will be a promise of the subclass type. To do so, the subclass’s constructor must implement the same signature as the Promise() constructor — accepting a single executor function that can be called with the resolve and reject callbacks as parameters. The subclass must also have a resolve static method that can be called like Promise.resolve() to resolve values to promises.
Note that JavaScript is single-threaded by nature, so at a given instant, only one task will be executing, although control can shift between different promises, making execution of the promises appear concurrent. Parallel execution in JavaScript can only be achieved through worker threads.
Constructor
Creates a new Promise object. The constructor is primarily used to wrap functions that do not already support promises.
Static properties
Returns the constructor used to construct return values from promise methods.
Static methods
Takes an iterable of promises as input and returns a single Promise . This returned promise fulfills when all of the input’s promises fulfill (including when an empty iterable is passed), with an array of the fulfillment values. It rejects when any of the input’s promises reject, with this first rejection reason.
Takes an iterable of promises as input and returns a single Promise . This returned promise fulfills when all of the input’s promises settle (including when an empty iterable is passed), with an array of objects that describe the outcome of each promise.
Takes an iterable of promises as input and returns a single Promise . This returned promise fulfills when any of the input’s promises fulfill, with this first fulfillment value. It rejects when all of the input’s promises reject (including when an empty iterable is passed), with an AggregateError containing an array of rejection reasons.
Takes an iterable of promises as input and returns a single Promise . This returned promise settles with the eventual state of the first promise that settles.
Returns a new Promise object that is rejected with the given reason.
Returns a Promise object that is resolved with the given value. If the value is a thenable (i.e. has a then method), the returned promise will «follow» that thenable, adopting its eventual state; otherwise, the returned promise will be fulfilled with the value.
Instance properties
These properties are defined on Promise.prototype and shared by all Promise instances.
The constructor function that created the instance object. For Promise instances, the initial value is the Promise constructor.
The initial value of the @@toStringTag property is the string «Promise» . This property is used in Object.prototype.toString() .
Instance methods
Appends a rejection handler callback to the promise, and returns a new promise resolving to the return value of the callback if it is called, or to its original fulfillment value if the promise is instead fulfilled.
Appends a handler to the promise, and returns a new promise that is resolved when the original promise is resolved. The handler is called when the promise is settled, whether fulfilled or rejected.
Appends fulfillment and rejection handlers to the promise, and returns a new promise resolving to the return value of the called handler, or to its original settled value if the promise was not handled (i.e. if the relevant handler onFulfilled or onRejected is not a function).
Examples
Basic Example
const myFirstPromise = new Promise((resolve, reject) => // We call resolve(. ) when what we were doing asynchronously was successful, and reject(. ) when it failed. // In this example, we use setTimeout(. ) to simulate async code. // In reality, you will probably be using something like XHR or an HTML API. setTimeout(() => resolve("Success!"); // Yay! Everything went well! >, 250); >); myFirstPromise.then((successMessage) => // successMessage is whatever we passed in the resolve(. ) function above. // It doesn't have to be a string, but if it is only a succeed message, it probably will be. console.log(`Yay! $successMessage>`); >);
Example with diverse situations
This example shows diverse techniques for using Promise capabilities and diverse situations that can occur. To understand this, start by scrolling to the bottom of the code block, and examine the promise chain. Upon provision of an initial promise, a chain of promises can follow. The chain is composed of .then() calls, and typically (but not necessarily) has a single .catch() at the end, optionally followed by .finally() . In this example, the promise chain is initiated by a custom-written new Promise() construct; but in actual practice, promise chains more typically start with an API function (written by someone else) that returns a promise.
The example function tetheredGetNumber() shows that a promise generator will utilize reject() while setting up an asynchronous call, or within the call-back, or both. The function promiseGetWord() illustrates how an API function might generate and return a promise in a self-contained manner.
Note that the function troubleWithGetNumber() ends with a throw . That is forced because a promise chain goes through all the .then() promises, even after an error, and without the throw , the error would seem «fixed». This is a hassle, and for this reason, it is common to omit onRejected throughout the chain of .then() promises, and just have a single onRejected in the final catch() .
This code can be run under NodeJS. Comprehension is enhanced by seeing the errors actually occur. To force more errors, change the threshold values.
// To experiment with error handling, "threshold" values cause errors randomly const THRESHOLD_A = 8; // can use zero 0 to guarantee error function tetheredGetNumber(resolve, reject) setTimeout(() => const randomInt = Date.now(); const value = randomInt % 10; if (value THRESHOLD_A) resolve(value); > else reject(`Too large: $value>`); > >, 500); > function determineParity(value) const isOdd = value % 2 === 1; return value, isOdd >; > function troubleWithGetNumber(reason) const err = new Error("Trouble getting number", cause: reason >); console.error(err); throw err; > function promiseGetWord(parityInfo) return new Promise((resolve, reject) => const value, isOdd > = parityInfo; if (value >= THRESHOLD_A - 1) reject(`Still too large: $value>`); > else parityInfo.wordEvenOdd = isOdd ? "odd" : "even"; resolve(parityInfo); > >); > new Promise(tetheredGetNumber) .then(determineParity, troubleWithGetNumber) .then(promiseGetWord) .then((info) => console.log(`Got: $info.value>, $info.wordEvenOdd>`); return info; >) .catch((reason) => if (reason.cause) console.error("Had previously handled error"); > else console.error(`Trouble with promiseGetWord(): $reason>`); > >) .finally((info) => console.log("All done"));
Advanced Example
This small example shows the mechanism of a Promise . The testPromise() method is called each time the is clicked. It creates a promise that will be fulfilled, using setTimeout() , to the promise count (number starting from 1) every 1-3 seconds, at random. The Promise() constructor is used to create the promise.
The fulfillment of the promise is logged, via a fulfill callback set using p1.then() . A few logs show how the synchronous part of the method is decoupled from the asynchronous completion of the promise.
By clicking the button several times in a short amount of time, you’ll even see the different promises being fulfilled one after another.
HTML
button id="make-promise">Make a promise!button> div id="log">div>
JavaScript
"use strict"; let promiseCount = 0; function testPromise() const thisPromiseCount = ++promiseCount; const log = document.getElementById("log"); // begin log.insertAdjacentHTML("beforeend", `$thisPromiseCount>) Started `); // We make a new promise: we promise a numeric count of this promise, // starting from 1 (after waiting 3s) const p1 = new Promise((resolve, reject) => // The executor function is called with the ability // to resolve or reject the promise log.insertAdjacentHTML( "beforeend", `$thisPromiseCount>) Promise constructor `, ); // This is only an example to create asynchronism setTimeout( () => // We fulfill the promise resolve(thisPromiseCount); >, Math.random() * 2000 + 1000, ); >); // We define what to do when the promise is resolved with the then() call, // and what to do when the promise is rejected with the catch() call p1.then((val) => // Log the fulfillment value log.insertAdjacentHTML("beforeend", `$val>) Promise fulfilled `); >).catch((reason) => // Log the rejection reason console.log(`Handle rejected promise ($reason>) here.`); >); // end log.insertAdjacentHTML("beforeend", `$thisPromiseCount>) Promise made `); > const btn = document.getElementById("make-promise"); btn.addEventListener("click", testPromise);
Result
Loading an image with XHR
Another simple example using Promise and XMLHttpRequest to load an image is available at the MDN GitHub js-examples repository. You can also see it in action. Each step is commented on and allows you to follow the Promise and XHR architecture closely.
Incumbent settings object tracking
A settings object is an environment that provides additional information when JavaScript code is running. This includes the realm and module map, as well as HTML specific information such as the origin. The incumbent settings object is tracked in order to ensure that the browser knows which one to use for a given piece of user code.
To better picture this, we can take a closer look at how the realm might be an issue. A realm can be roughly thought of as the global object. What is unique about realms is that they hold all of the necessary information to run JavaScript code. This includes objects like Array and Error . Each settings object has its own «copy» of these and they are not shared. That can cause some unexpected behavior in relation to promises. In order to get around this, we track something called the incumbent settings object. This represents information specific to the context of the user code responsible for a certain function call.
doctype html> iframe>iframe> script> // we have a realm here as well const bound = frames[0].postMessage.bind(frames[0], "some data", "*"); // bound is a built-in function — there is no user // code on the stack, so which realm do we use? setTimeout(bound); // this still works, because we use the youngest // realm (the incumbent) on the stack script>
The same concept applies to promises. If we modify the above example a little bit, we get this:
doctype html> iframe>iframe> script> // we have a realm here as well const bound = frames[0].postMessage.bind(frames[0], "some data", "*"); // bound is a built in function — there is no user // code on the stack — which realm do we use? Promise.resolve(undefined).then(bound); // this still works, because we use the youngest // realm (the incumbent) on the stack script>
If we change this so that the in the document is listening to post messages, we can observe the effect of the incumbent settings object:
doctype html> iframe src="x.html">iframe> script> const bound = frames[0].postMessage.bind(frames[0], "some data", "*"); Promise.resolve(undefined).then(bound); script>doctype html> script> window.addEventListener( "message", (event) => document.querySelector("#text").textContent = "hello"; // this code will only run in browsers that track the incumbent settings object console.log(event); >, false, ); script>In the above example, the inner text of the will be updated only if the incumbent settings object is tracked. This is because without tracking the incumbent, we may end up using the wrong environment to send the message.
Note: Currently, incumbent realm tracking is fully implemented in Firefox, and has partial implementations in Chrome and Safari.
Specifications
Specification ECMAScript Language Specification
# sec-promise-objectsBrowser compatibility
BCD tables only load in the browser
See also
- Polyfill of Promise in core-js
- Using promises guide
- Promises/A+ specification
- JavaScript Promises: an introduction on web.dev (2013)
- Callbacks, Promises, and Coroutines: Asynchronous Programming Patterns in JavaScript slide show by Domenic Denicola (2011)
Found a content problem with this page?
- Edit the page on GitHub.
- Report the content issue.
- View the source on GitHub.
This page was last modified on Oct 25, 2023 by MDN contributors.
Your blueprint for a better internet.
MDN
Support
- Product help
- Report an issue
Our communities
Developers
- Web Technologies
- Learn Web Development
- MDN Plus
- Hacks Blog
- Website Privacy Notice
- Cookies
- Legal
- Community Participation Guidelines
Visit Mozilla Corporation’s not-for-profit parent, the Mozilla Foundation.
Portions of this content are ©1998– 2023 by individual mozilla.org contributors. Content available under a Creative Commons license.Promise
Материал на этой странице устарел, поэтому скрыт из оглавления сайта.
Более новая информация по этой теме находится на странице https://learn.javascript.ru/promise-basics.
Promise (обычно их так и называют «промисы») – предоставляют удобный способ организации асинхронного кода.
В современном JavaScript промисы часто используются в том числе и неявно, при помощи генераторов, но об этом чуть позже.
Что такое Promise?
Promise – это специальный объект, который содержит своё состояние. Вначале pending («ожидание»), затем – одно из: fulfilled («выполнено успешно») или rejected («выполнено с ошибкой»).

На promise можно навешивать колбэки двух типов:
- onFulfilled – срабатывают, когда promise в состоянии «выполнен успешно».
- onRejected – срабатывают, когда promise в состоянии «выполнен с ошибкой».
Способ использования, в общих чертах, такой:
- Код, которому надо сделать что-то асинхронно, создаёт объект promise и возвращает его.
- Внешний код, получив promise , навешивает на него обработчики.
- По завершении процесса асинхронный код переводит promise в состояние fulfilled (с результатом) или rejected (с ошибкой). При этом автоматически вызываются соответствующие обработчики во внешнем коде.
Синтаксис создания Promise :
var promise = new Promise(function(resolve, reject) < // Эта функция будет вызвана автоматически // В ней можно делать любые асинхронные операции, // А когда они завершатся — нужно вызвать одно из: // resolve(результат) при успешном выполнении // reject(ошибка) при ошибке >)Универсальный метод для навешивания обработчиков:
promise.then(onFulfilled, onRejected)- onFulfilled – функция, которая будет вызвана с результатом при resolve .
- onRejected – функция, которая будет вызвана с ошибкой при reject .
С его помощью можно назначить как оба обработчика сразу, так и только один:
// onFulfilled сработает при успешном выполнении promise.then(onFulfilled) // onRejected сработает при ошибке promise.then(null, onRejected)Для того, чтобы поставить обработчик только на ошибку, вместо .then(null, onRejected) можно написать .catch(onRejected) – это то же самое.
Синхронный throw – то же самое, что reject
Если в функции промиса происходит синхронный throw (или иная ошибка), то вызывается reject :
'use strict'; let p = new Promise((resolve, reject) => < // то же что reject(new Error("o_O")) throw new Error("o_O"); >) p.catch(alert); // Error: o_OПосмотрим, как это выглядит вместе, на простом примере.
Пример с setTimeout
Возьмём setTimeout в качестве асинхронной операции, которая должна через некоторое время успешно завершиться с результатом «result»:
'use strict'; // Создаётся объект promise let promise = new Promise((resolve, reject) => < setTimeout(() =>< // переведёт промис в состояние fulfilled с результатом "result" resolve("result"); >, 1000); >); // promise.then навешивает обработчики на успешный результат или ошибку promise .then( result => < // первая функция-обработчик - запустится при вызове resolve alert("Fulfilled: " + result); // result - аргумент resolve >, error => < // вторая функция - запустится при вызове reject alert("Rejected: " + error); // error - аргумент reject >);В результате запуска кода выше – через 1 секунду выведется «Fulfilled: result».
А если бы вместо resolve(«result») был вызов reject(«error») , то вывелось бы «Rejected: error». Впрочем, как правило, если при выполнении возникла проблема, то reject вызывают не со строкой, а с объектом ошибки типа new Error :
// Этот promise завершится с ошибкой через 1 секунду var promise = new Promise((resolve, reject) => < setTimeout(() =>< reject(new Error("время вышло!")); >, 1000); >); promise .then( result => alert("Fulfilled: " + result), error => alert("Rejected: " + error.message) // Rejected: время вышло! );Конечно, вместо setTimeout внутри функции промиса может быть и запрос к серверу и ожидание ввода пользователя, или другой асинхронный процесс. Главное, чтобы по своему завершению он вызвал resolve или reject , которые передадут результат обработчикам.
Только один аргумент
Функции resolve/reject принимают ровно один аргумент – результат/ошибку.
Именно он передаётся обработчикам в .then , как можно видеть в примерах выше.
Promise после reject/resolve – неизменны
Заметим, что после вызова resolve/reject промис уже не может «передумать».
Когда промис переходит в состояние «выполнен» – с результатом (resolve) или ошибкой (reject) – это навсегда.
'use strict'; let promise = new Promise((resolve, reject) => < // через 1 секунду готов результат: result setTimeout(() =>resolve("result"), 1000); // через 2 секунды — reject с ошибкой, он будет проигнорирован setTimeout(() => reject(new Error("ignored")), 2000); >); promise .then( result => alert("Fulfilled: " + result), // сработает error => alert("Rejected: " + error) // не сработает );В результате вызова этого кода сработает только первый обработчик then , так как после вызова resolve промис уже получил состояние (с результатом), и в дальнейшем его уже ничто не изменит.
Последующие вызовы resolve/reject будут просто проигнорированы.
А так – наоборот, ошибка будет раньше:
'use strict'; let promise = new Promise((resolve, reject) => < // reject вызван раньше, resolve будет проигнорирован setTimeout(() =>reject(new Error("error")), 1000); setTimeout(() => resolve("ignored"), 2000); >); promise .then( result => alert("Fulfilled: " + result), // не сработает error => alert("Rejected: " + error) // сработает );Промисификация
Промисификация – это когда берут асинхронную функциональность и делают для неё обёртку, возвращающую промис.
После промисификации использование функциональности зачастую становится гораздо удобнее.
В качестве примера сделаем такую обёртку для запросов при помощи XMLHttpRequest.
Функция httpGet(url) будет возвращать промис, который при успешной загрузке данных с url будет переходить в fulfilled с этими данными, а при ошибке – в rejected с информацией об ошибке:
function httpGet(url) < return new Promise(function(resolve, reject) < var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onload = function() < if (this.status == 200) < resolve(this.response); >else < var error = new Error(this.statusText); error.code = this.status; reject(error); >>; xhr.onerror = function() < reject(new Error("Network Error")); >; xhr.send(); >); >Как видно, внутри функции объект XMLHttpRequest создаётся и отсылается как обычно, при onload/onerror вызываются, соответственно, resolve (при статусе 200) или reject .
httpGet("/article/promise/user.json") .then( response => alert(`Fulfilled: $`), error => alert(`Rejected: $`) );Метод fetch
Заметим, что ряд современных браузеров уже поддерживает fetch – новый встроенный метод для AJAX-запросов, призванный заменить XMLHttpRequest. Он гораздо мощнее, чем httpGet . И – да, этот метод использует промисы. Полифил для него доступен на https://github.com/github/fetch.
Цепочки промисов
«Чейнинг» (chaining), то есть возможность строить асинхронные цепочки из промисов – пожалуй, основная причина, из-за которой существуют и активно используются промисы.
Например, мы хотим по очереди:
- Загрузить данные посетителя с сервера (асинхронно).
- Затем отправить запрос о нём на github (асинхронно).
- Когда это будет готово, вывести его github-аватар на экран (асинхронно).
- …И сделать код расширяемым, чтобы цепочку можно было легко продолжить.
Вот код для этого, использующий функцию httpGet , описанную выше:
'use strict'; // сделать запрос httpGet('/article/promise/user.json') // 1. Получить данные о пользователе в JSON и передать дальше .then(response => < console.log(response); let user = JSON.parse(response); return user; >) // 2. Получить информацию с github .then(user => < console.log(user); return httpGet(`https://api.github.com/users/$`); >) // 3. Вывести аватар на 3 секунды (можно с анимацией) .then(githubUser => < console.log(githubUser); githubUser = JSON.parse(githubUser); let img = new Image(); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.appendChild(img); setTimeout(() =>img.remove(), 3000); // (*) >);Самое главное в этом коде – последовательность вызовов:
httpGet(. ) .then(. ) .then(. ) .then(. )При чейнинге, то есть последовательных вызовах .then…then…then , в каждый следующий then переходит результат от предыдущего. Вызовы console.log оставлены, чтобы при запуске можно было посмотреть конкретные значения, хотя они здесь и не очень важны.
Если очередной then вернул промис, то далее по цепочке будет передан не сам этот промис, а его результат.
- Функция в первом then возвращает «обычное» значение user . Это значит, что then возвратит промис в состоянии «выполнен» с user в качестве результата. Он станет аргументом в следующем then .
- Функция во втором then возвращает промис (результат нового вызова httpGet ). Когда он будет завершён (может пройти какое-то время), то будет вызван следующий then с его результатом.
- Третий then ничего не возвращает.
Схематично его работу можно изобразить так:

Значком «песочные часы» помечены периоды ожидания, которых всего два: в исходном httpGet и в подвызове далее по цепочке.
Если then возвращает промис, то до его выполнения может пройти некоторое время, оставшаяся часть цепочки будет ждать.
То есть, логика довольно проста:
- В каждом then мы получаем текущий результат работы.
- Можно его обработать синхронно и вернуть результат (например, применить JSON.parse ). Или же, если нужна асинхронная обработка – инициировать её и вернуть промис.
Обратим внимание, что последний then в нашем примере ничего не возвращает. Если мы хотим, чтобы после setTimeout (*) асинхронная цепочка могла быть продолжена, то последний then тоже должен вернуть промис. Это общее правило: если внутри then стартует новый асинхронный процесс, то для того, чтобы оставшаяся часть цепочки выполнилась после его окончания, мы должны вернуть промис.
В данном случае промис должен перейти в состояние «выполнен» после срабатывания setTimeout .
Строку (*) для этого нужно переписать так:
.then(githubUser => < . // вместо setTimeout(() =>img.remove(), 3000); (*) return new Promise((resolve, reject) => < setTimeout(() =>< img.remove(); // после таймаута — вызов resolve, // можно без результата, чтобы управление перешло в следующий then // (или можно передать данные пользователя дальше по цепочке) resolve(); >, 3000); >); >)Теперь, если к цепочке добавить ещё then , то он будет вызван после окончания setTimeout .
Перехват ошибок
Выше мы рассмотрели «идеальный случай» выполнения, когда ошибок нет.
А что, если github не отвечает? Или JSON.parse бросил синтаксическую ошибку при обработке данных?
Да мало ли, где ошибка…
Правило здесь очень простое.
При возникновении ошибки – она отправляется в ближайший обработчик onRejected .
Такой обработчик нужно поставить через второй аргумент .then(. onRejected) или, что то же самое, через .catch(onRejected) .
Чтобы поймать всевозможные ошибки, которые возникнут при загрузке и обработке данных, добавим catch в конец нашей цепочки:
'use strict'; // в httpGet обратимся к несуществующей странице httpGet('/page-not-exists') .then(response => JSON.parse(response)) .then(user => httpGet(`https://api.github.com/users/$`)) .then(githubUser => < githubUser = JSON.parse(githubUser); let img = new Image(); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.appendChild(img); return new Promise((resolve, reject) => < setTimeout(() =>< img.remove(); resolve(); >, 3000); >); >) .catch(error => < alert(error); // Error: Not Found >);В примере выше ошибка возникает в первом же httpGet , но catch с тем же успехом поймал бы ошибку во втором httpGet или в JSON.parse .
Принцип очень похож на обычный try..catch : мы делаем асинхронную цепочку из .then , а затем, в том месте кода, где нужно перехватить ошибки, вызываем .catch(onRejected) .
А что после catch ?
Обработчик .catch(onRejected) получает ошибку и должен обработать её.
Есть два варианта развития событий:
- Если ошибка не критичная, то onRejected возвращает значение через return , и управление переходит в ближайший .then(onFulfilled) .
- Если продолжить выполнение с такой ошибкой нельзя, то он делает throw , и тогда ошибка переходит в следующий ближайший .catch(onRejected) .
Это также похоже на обычный try..catch – в блоке catch ошибка либо обрабатывается, и тогда выполнение кода продолжается как обычно, либо он делает throw . Существенное отличие – в том, что промисы асинхронные, поэтому при отсутствии внешнего .catch ошибка не «вываливается» в консоль и не «убивает» скрипт.
Ведь возможно, что новый обработчик .catch будет добавлен в цепочку позже.
Промисы в деталях
Самым основным источником информации по промисам является, разумеется, стандарт.
Чтобы наше понимание промисов было полным, и мы могли с лёгкостью разрешать сложные ситуации, посмотрим внимательнее, что такое промис и как он работает, но уже не в общих словах, а детально, в соответствии со стандартом ECMAScript.
Согласно стандарту, у объекта new Promise(executor) при создании есть четыре внутренних свойства:

- PromiseState – состояние, вначале «pending».
- PromiseResult – результат, при создании значения нет.
- PromiseFulfillReactions – список функций-обработчиков успешного выполнения.
- PromiseRejectReactions – список функций-обработчиков ошибки.
Когда функция-executor вызывает reject или resolve , то PromiseState становится «resolved» или «rejected» , а все функции-обработчики из соответствующего списка перемещаются в специальную системную очередь «PromiseJobs» .
Эта очередь автоматически выполняется, когда интерпретатору «нечего делать». Иначе говоря, все функции-обработчики выполнятся асинхронно, одна за другой, по завершении текущего кода, примерно как setTimeout(. 0) .
Исключение из этого правила – если resolve возвращает другой Promise . Тогда дальнейшее выполнение ожидает его результата (в очередь помещается специальная задача), и функции-обработчики выполняются уже с ним.
Добавляет обработчики в списки один метод: .then(onResolved, onRejected) . Метод .catch(onRejected) – всего лишь сокращённая запись .then(null, onRejected) .
Он делает следующее:
- Если PromiseState == «pending» , то есть промис ещё не выполнен, то обработчики добавляются в соответствующие списки.
- Иначе обработчики сразу помещаются в очередь на выполнение.
Здесь важно, что обработчики можно добавлять в любой момент. Можно до выполнения промиса (они подождут), а можно – после (выполнятся в ближайшее время, через асинхронную очередь).
// Промис выполнится сразу же var promise = new Promise((resolve, reject) => resolve(1)); // PromiseState = "resolved" // PromiseResult = 1 // Добавили обработчик к выполненному промису promise.then(alert); // . он сработает тут жеРазумеется, можно добавлять и много обработчиков на один и тот же промис:
// Промис выполнится сразу же var promise = new Promise((resolve, reject) => resolve(1)); promise.then( function f1(result) < alert(result); // 1 return 'f1'; >) promise.then( function f2(result) < alert(result); // 1 return 'f2'; >)Вид объекта promise после этого:

На этой иллюстрации можно увидеть добавленные нами обработчики f1 , f2 , а также – автоматически добавленные обработчики ошибок «Thrower» .
Дело в том, что .then , если один из обработчиков не указан, добавляет его «от себя», следующим образом:
- Для успешного выполнения – функция Identity , которая выглядит как arg => arg , то есть возвращает аргумент без изменений.
- Для ошибки – функция Thrower , которая выглядит как arg => throw arg , то есть генерирует ошибку.
Это, по сути дела, формальность, но без неё некоторые особенности поведения промисов могут «не сойтись» в общую логику, поэтому мы упоминаем о ней здесь.
Обратим внимание, в этом примере намеренно не используется чейнинг. То есть, обработчики добавляются именно на один и тот же промис.
Поэтому оба alert выдадут одно значение 1 .
Все функции из списка обработчиков вызываются с результатом промиса, одна за другой. Никакой передачи результатов между обработчиками в рамках одного промиса нет, а сам результат промиса ( PromiseResult ) после установки не меняется.
Поэтому, чтобы продолжить работу с результатом, используется чейнинг.
Для того, чтобы результат обработчика передать следующей функции, .then создаёт новый промис и возвращает его.
В примере выше создаётся два таких промиса (т.к. два вызова .then ), каждый из которых даёт свою ветку выполнения:

Изначально эти новые промисы – «пустые», они ждут. Когда в будущем выполнятся обработчики f1, f2 , то их результат будет передан в новые промисы по стандартному принципу:

- Если вернётся обычное значение (не промис), новый промис перейдёт в «resolved» с ним.
- Если был throw , то новый промис перейдёт в состояние «rejected» с ошибкой.
- Если вернётся промис, то используем его результат (он может быть как resolved , так и rejected ).
Дальше выполнятся уже обработчики на новом промисе, и так далее.
Чтобы лучше понять происходящее, посмотрим на цепочку, которая получается в процессе написания кода для показа github-аватара.
Первый промис и обработка его результата:
httpGet('/article/promise/user.json') .then(JSON.parse)Если промис завершился через resolve , то результат – в JSON.parse , если reject – то в Thrower.
Как было сказано выше, Thrower – это стандартная внутренняя функция, которая автоматически используется, если второй обработчик не указан.
Можно считать, что второй обработчик выглядит так:
httpGet('/article/promise/user.json') .then(JSON.parse, err => throw err)Заметим, что когда обработчик в промисах делает throw – в данном случае, при ошибке запроса, то такая ошибка не «валит» скрипт и не выводится в консоли. Она просто будет передана в ближайший следующий обработчик onRejected .
Добавим в код ещё строку:
httpGet('/article/promise/user.json') .then(JSON.parse) .then(user => httpGet(`https://api.github.com/users/$`))Цепочка «выросла вниз»:
Функция JSON.parse либо возвращает объект с данными, либо генерирует ошибку (что расценивается как reject ).
Если всё хорошо, то then(user => httpGet(…)) вернёт новый промис, на который стоят уже два обработчика:
httpGet('/article/promise/user.json') .then(JSON.parse) .then(user => httpGet(`https://api.github.com/users/$`)) .then( JSON.parse, function avatarError(error) < if (error.code == 404) < return ; > else < throw error; >> >)Наконец-то хоть какая-то обработка ошибок!
Обработчик avatarError перехватит ошибки, которые были ранее. Функция httpGet при генерации ошибки записывает её HTTP-код в свойство error.code , так что мы легко можем понять – что это:
- Если страница на Github не найдена – можно продолжить выполнение, используя «аватар по умолчанию»
- Иначе – пробрасываем ошибку дальше.
Итого, после добавления оставшейся части цепочки, картина получается следующей:
'use strict'; httpGet('/article/promise/userNoGithub.json') .then(JSON.parse) .then(user => httpGet(`https://api.github.com/users/$`)) .then( JSON.parse, function githubError(error) < if (error.code == 404) < return ; > else < throw error; >> ) .then(function showAvatar(githubUser) < let img = new Image(); img.src = githubUser.avatar_url; img.className = "promise-avatar-example"; document.body.appendChild(img); setTimeout(() =>img.remove(), 3000); >) .catch(function genericError(error) < alert(error); // Error: Not Found >);В конце срабатывает общий обработчик genericError , который перехватывает любые ошибки. В данном случае ошибки, которые в него попадут, уже носят критический характер, что-то серьёзно не так. Чтобы посетитель не удивился отсутствию информации, мы показываем ему сообщение об этом.
Можно и как-то иначе вывести уведомление о проблеме, главное – не забыть обработать ошибки в конце. Если последнего catch не будет, а цепочка завершится с ошибкой, то посетитель об этом не узнает.
В консоли тоже ничего не будет, так как ошибка остаётся «внутри» промиса, ожидая добавления следующего обработчика onRejected , которому будет передана.
Итак, мы рассмотрели основные приёмы использования промисов. Далее – посмотрим некоторые полезные вспомогательные методы.
Параллельное выполнение
Что, если мы хотим осуществить несколько асинхронных процессов одновременно и обработать их результат?
В классе Promise есть следующие статические методы.
Promise.all(iterable)
Вызов Promise.all(iterable) получает массив (или другой итерируемый объект) промисов и возвращает промис, который ждёт, пока все переданные промисы завершатся, и переходит в состояние «выполнено» с массивом их результатов.
Promise.all([ httpGet('/article/promise/user.json'), httpGet('/article/promise/guest.json') ]).then(results => < alert(results); >);Допустим, у нас есть массив с URL.
let urls = [ '/article/promise/user.json', '/article/promise/guest.json' ];Чтобы загрузить их параллельно, нужно:
- Создать для каждого URL соответствующий промис.
- Обернуть массив таких промисов в Promise.all .
'use strict'; let urls = [ '/article/promise/user.json', '/article/promise/guest.json' ]; Promise.all( urls.map(httpGet) ) .then(results => < alert(results); >);Заметим, что если какой-то из промисов завершился с ошибкой, то результатом Promise.all будет эта ошибка. При этом остальные промисы игнорируются.
Promise.all([ httpGet('/article/promise/user.json'), httpGet('/article/promise/guest.json'), httpGet('/article/promise/no-such-page.json') // (нет такой страницы) ]).then( result => alert("не сработает"), error => alert("Ошибка: " + error.message) // Ошибка: Not Found )Promise.race(iterable)
Вызов Promise.race , как и Promise.all , получает итерируемый объект с промисами, которые нужно выполнить, и возвращает новый промис.
Но, в отличие от Promise.all , результатом будет только первый успешно выполнившийся промис из списка. Остальные игнорируются.
Promise.race([ httpGet('/article/promise/user.json'), httpGet('/article/promise/guest.json') ]).then(firstResult => < firstResult = JSON.parse(firstResult); alert( firstResult.name ); // iliakan или guest, смотря что загрузится раньше >);Promise.resolve(value)
Вызов Promise.resolve(value) создаёт успешно выполнившийся промис с результатом value .
Он аналогичен конструкции:
new Promise((resolve) => resolve(value))Promise.resolve используют, когда хотят построить асинхронную цепочку, и начальный результат уже есть.
Promise.resolve(window.location) // начать с этого значения .then(httpGet) // вызвать для него httpGet .then(alert) // и вывести результатPromise.reject(error)
Аналогично Promise.reject(error) создаёт уже выполнившийся промис, но не с успешным результатом, а с ошибкой error .
Promise.reject(new Error(". ")) .catch(alert) // Error: .Метод Promise.reject используется очень редко, гораздо реже чем resolve , потому что ошибка возникает обычно не в начале цепочки, а в процессе её выполнения.
Итого
- Промис – это специальный объект, который хранит своё состояние, текущий результат (если есть) и колбэки.
- При создании new Promise((resolve, reject) => . ) автоматически запускается функция-аргумент, которая должна вызвать resolve(result) при успешном выполнении и reject(error) – при ошибке.
- Аргумент resolve/reject (только первый, остальные игнорируются) передаётся обработчикам на этом промисе.
- Обработчики назначаются вызовом .then/catch .
- Для передачи результата от одного обработчика к другому используется чейнинг.
У промисов есть некоторые ограничения. В частности, стандарт не предусматривает какой-то метод для «отмены» промиса, хотя в ряде ситуаций (http-запросы) это было бы довольно удобно. Возможно, он появится в следующей версии стандарта JavaScript.
В современной JavaScript-разработке сложные цепочки с промисами используются редко, так как они куда проще описываются при помощи генераторов с библиотекой co , которые рассмотрены в соответствующей главе. Можно сказать, что промисы лежат в основе более продвинутых способов асинхронной разработки.
Задачи
Промисифицировать setTimeout
Напишите функцию delay(ms) , которая возвращает промис, переходящий в состояние «resolved» через ms миллисекунд.
delay(1000) .then(() => alert("Hello!"))Такая функция полезна для использования в других промис-цепочках.
Вот такой вызов:
return new Promise((resolve, reject) => < setTimeout(() =>< doSomeThing(); resolve(); >, ms) >);Станет возможным переписать так:
return delay(ms).then(doSomething);
