Promise API
1. Введение
Promise (обещание, промис) — объект, представляющий текущее состояние асинхронной операции. Удобный способ организации асинхронного кода.
У промиса есть 2 состояния:
Pending — ожидание, исходное состояние при создании промиса.
Settled — выполнен, которое в свою очередь делится на две категории:
fullfilled
— выполнено успешно иrejected
— выполнено с ошибкой.
Вначале промис находится в состоянии ожидания (pending
), после чего он может выполнится успешно (fulfilled
) или с ошибкой (rejected
). Когда промис переходит в состояние выполнен (settled
), с результатом или ошибкой – это навсегда. Грубо говоря, промис - это болванка для данных, значение которых мы не знаем в момент его создания.

Способ использования:
Код, которому надо сделать что-то асинхронно, создаёт обещание и возвращает его.
Внешний код, получив обещание, навешивает на него обработчики.
По завершении процесса асинхронный код переводит обещание в состояние
fulfilled
илиrejected
. При этом автоматически вызываются обработчики во внешнем коде.

Отличия промиса и callback-функции:
Коллбэки - это функции, обещания это объекты.
Коллбэки передаются в качестве аргументов из внешнего кода во внутренний, обещания возвращаются из внутреннего кода во внешний.
Коллбэки обрабатывают успешное или неуспешное завершение, обещания ничего не обрабатывают.
Коллбэки могут обрабатывать несколько событий, обещания связаны только с одним событием.
Обещания (помимо читабельности, которая является побочным положительным эффектом) используются в основном для композиции, цепочки вызовов и обработки результата и ошибок в отдельных логических ветках.
2. Создание
Обещание создается как экземпляр класса Promise
с одной функцией в качестве аргумента. Вызов конструктора немедленно исполнит функцию fn
, переданную в качестве аргумента. Цель этой функции состоит в информировании экземпляра (промиса), когда событие, с которым он связан, будет завершено.
const promise = new Promise((resolve, reject) => {
/*
* Эта функция будет вызвана автоматически. В ней можно выполнять
* любые асинхронные операции. Когда они завершатся — нужно
* вызвать одно из: resolve(результат) при успешном выполнении,
* или reject(ошибка) при ошибке.
*/
});
Copy
Параметры передаваемой функции:
resolve(arg)
— функция, которую необходимо вызвать при успешной операции. Переданный в нее аргумент будет значением выполненного промиса.reject(arg)
— функция, которую необходимо вызвать при ошибке. Переданный в нее аргумент будет значением ошибки которое можно будет обработать.

const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success!');
}, 2000);
});
Copy
В переменную promise
будет записан объект обещания в состоянии pending
, а через 2 секунды, после того как будет вызван resolve("success!")
, промис перейдет в состояние fullfilled
.
3. Использование
После того как промис создан, с ним можно работать используя методы then
и catch
, которые доступны через его прототип. Код пишется так, как будто мы размышляем о том, что может произойти если промис выполнится или нет, не думая о временных рамках.
3.1. then
Позволяет выполнить код, в котором можно получить доступ и обработать результат промиса.
promise.then(onResolve, onReject)
Copy
В метод then
передаются две функции, которые будут вызваны когда промис перейдет в состояние выполнен (settled).
onResolve(arg)
— будет вызвана при успешном выполнении промиса и получит результат промиса как аргумент (то, что передаем в вызовresolve
).onReject(arg)
— будет вызвана при выполнении промиса с ошибкой и получит ошибку как аргумент (то, что передаем в вызовreject
).

const promise = new Promise((resolve, reject) => {
setTimeout(() => {
/*
* Если какое-то условие выполняется, то есть все хорошо,
* вызываем resolve и получает данные. Условие зависит от задачи.
*/
resolve('Data passed into resolve function :)');
// Если что-то не так, вызываем reject и передаем ошибку
// reject("Error passed into reject function :(")
}, 2000);
});
// Выполнится мгновенно
console.log('BEFORE promise.then');
const onResolve = data => {
console.log('INSIDE promise.then - onResolve');
console.log(data); // "Data passed into resolve function :)"
};
const onReject = error => {
console.log('INSIDE promise.then - onReject');
console.log(error); // "Error passed into reject function :("
};
promise.then(
// будет вызвана через 2 секунды, если обещание выполнится успешно
onResolve,
// будет вызвана через 2 секунды, если обещание выполнится с ошибкой
onReject,
);
// Выполнится мгновенно
console.log('AFTER promise.then');
Copy
Если onResolve
и onReject
не содержат сложной логики, их объявляют как инлайн функции в методе then
.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
// Если все ок, то вызывается resolve и передаем данные
resolve('Data passed into resolve function :)');
// Если что-то не так, вызваем reject и передаем ошибку
// reject("Error passed into reject function :(")
}, 2000);
});
// Выполнится мгновенно
console.log('BEFORE promise.then');
promise.then(
// Будет вызвана через 2 секунды, если обещание выполнится успешно
data => {
console.log('INSIDE promise.then - onResolve');
console.log(data); // "Data passed into resolve function :)"
},
// Будет вызвана через 2 секунды, если обещание выполнится с ошибкой
error => {
console.log('INSIDE promise.then - onReject');
console.log(error); // "Error passed into reject function :("
},
);
// Выполнится мгновенно
console.log('AFTER promise.then');
Copy
3.2. catch
Немного дальше мы узнаем о цепочках промисов, а пока научимся обрабатывать ошибки не в колбеке onReject
метода then
, а в специальном методе catch
. Обрабатывать ошибки очень удобно, используя метод catch
только один раз, в конце цепочки.
promise.catch(onReject)
Copy
Хендлер для обработки состояния reject
, исполнится только если промис исполнится с ошибкой (rejected). onReject(arg)
будет вызвана при выполнении промиса с ошибкой, и получит ошибку как аргумент (то, что передаем в вызов reject).
Создадим обещание, сделаем задержку на 2 секунды, вызовем reject
, имитируя выполнение промиса с ошибкой.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
reject('There was an error :(');
}, 2000);
});
/*
* then не выполнится так как в функции fn, внутри new Promise(fn),
* был вызван reject(). А catch как раз выполнится через 2 секунды
*/
promise
.then(data => {
console.log(data);
})
.catch(error => {
console.log(error);
});
Copy
3.3. finally
Этот метод может быть полезен, если вы хотите выполнить некоторую обработку или очистку после того, как обещание будет исполнено, независимо от результата.
Позволяет выполнить указанную callback-функцию после того, как обещание будет разрешено (выполнено или отклонено). Позволяет избежать дублирования кода в обработчиках then()
и catch()
. Возвращает обещание.
promise.finally(() => {
// settled (fulfilled или rejected)
});
Copy
Функция обратного вызова не получит никаких аргументов, поскольку нельзя точно определить выполнено ли обещание или отклонено. Тут будет выполняться код, который зависит только от времени его исполнения, значение промиса не важно.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve('success!');
}, 2000);
});
promise
.then(data => console.log(data)) // "success"
.catch(error => console.log(error))
.finally(() => console.log('finished!')); // "finished"
Copy
4. Цепочки промисов
Чейнинг (chaining) — возможность строить асинхронные цепочки из промисов. Одна из основных причин существования и активного использования промисов.
Каждый метод then
, результатом своего выполнения, возвращает промис. Его значением будет то, что возвращается из callback-функции onResolve
.

asyncFn(...)
.then(...)
.then(...)
.then(...)
.catch(...);
Copy
Так как then
возвращает промис, до его выполнения может пройти некоторое время, поэтому оставшаяся часть цепочки будет ждать.
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve(5);
}, 2000);
});
promise
.then(value => {
console.log(value); // 5
return value * 2;
})
.then(value => {
console.log(value); // 10
return value * 3;
})
.then(value => {
console.log(value); // 30
})
.catch(error => {
console.log(error);
});
Copy
При возникновении ошибки в любом месте цепочки, выполнение всех последующих then
отменяется, а управление передается методу catch
.
5. Статические методы класса Promise
Иногда бывают ситуации, когда нам необходимо дождаться выполнения не одного, а сразу набора промисов и только тогда что-то сделать. Бывают и такие ситуации, когда из набора промисов необходимо дождаться выполнения любого одного, проигнорировав остальные.
5.1. Promise.all()
Статический метод, получает массив промисов и ждет их исполнения, возвращает промис.
Promise.all([promise1, promise2, ...])
Copy
При успешном выполнении всех промисов из массива, промис, возвращаемый из Promise.all
, перейдет в состояние settled -> fullfilled
, а его значением будет массив результатов исполнения каждого промиса.
Если в массиве промисов хотя бы один исполнился с ошибкой, то перейдет в состояние settiled -> rejected
, а значением промиса будет ошибка.
Давайте напишем функцию, которая будет принимать текст для resolve
и задержку в мс, а результатом своего выполнения будет возвращать промис. Затем создадим 2 промиса с разным временем задержки.
const makePromise = (text, delay) => {
return new Promise(resolve => {
setTimeout(() => resolve(text), delay);
});
};
const promiseA = makePromise('promiseA', 1000);
const promiseB = makePromise('promiseB', 3000);
/*
* Выполнится спустя 3 секунды, когда выполнится второй промис с задержкой в 3c.
* Первый выполнится через секунду и просто будет готов
*/
Promise.all([promiseA, promiseB])
.then(result => console.log(result)) //["promiseA", "promiseB"]
.catch(err => console.log(err));
Copy
5.2. Promise.race()
Promise.race([promise1, promise2, ...])
Copy
Статический метод, получает массив промисов и возвращает обещание. Когда хотя бы одно обещание в массиве исполнилось, исполнится возвращаемый промис, а все остальные будут отброшены.
const makePromise = (text, delay) => {
return new Promise(resolve => {
setTimeout(() => resolve(text), delay);
});
};
const promiseA = makePromise('promiseA', 1000);
const promiseB = makePromise('promiseB', 3000);
/*
* Выполнится спустя 1 секунду, когда выполнится самый быстрый promiseA
* с задержкой в 1c. Второй промис promiseB будет проигнорирован
*/
Promise.race([promiseA, promiseB])
.then(result => console.log(result)) // "promiseA"
.catch(err => console.log(err));
Copy
6. Promise.resolve(), Promise.reject() и Promise.finally()
Вызов статического метода Promise.resolve(value)
создаёт успешно выполнившийся промис с результатом value
. Это аналогично new Promise((resolve) => resolve(value))
, только короче. Этот метод используют, когда хотят построить асинхронную цепочку и начальный результат уже есть.
Это можно использовать для того, чтобы заменить callback на цепочку промисов. То есть вместо, того чтобы передавать callback в функцию и надеяться на лучшее, получаем промис и чейним then
, в котором доступен результат работы функции.
const getMessage = function (callback) {
const input = prompt('Введите сообщение');
callback(input);
};
const logger = message => console.log(message);
getMessage(logger);
Copy
Превращается в следующее.
const getMessage = function () {
const input = prompt('Введите сообщение');
return Promise.resolve(input);
};
const logger = message => console.log(message);
getMessage().then(message => logger(message));
// Или еще короче
getMessage().then(logger);
Copy
Аналогично Promise.reject(error)
создаёт уже выполнившийся промис, но не с успешным результатом, а с ошибкой error
. Он используется крайне редко, потому что ошибка возникает обычно не в начале цепочки, а в процессе её выполнения.
7. Дополнительные материалы
Last updated