Промис (Обещание) — это объект, который содержит будущее значение асинхронной операции. Например, если вы запрашиваете некоторые данные с сервера, промис обещает нам получить эти данные, которые мы сможем использовать в будущем.
Промис в JavaScript имеет 3 состояния. Это может быть:
1) нерешенный (в ожидании) — Промис ожидает, если результат не готов. То есть, ожидает завершение чего-либо (например, завершения асинхронной операции).
2) решенный/resolved (выполненный) — Промис решен, если результат доступен. То есть, что-то завершило свое выполнение (например, асинхронная операция) и все прошло хорошо.
3) отклоненный/rejected — Промиc отклонен, если произошла ошибка в процессе выполнения.
Синтаксис создания Promise
:
const promise = new Promise(function(resolve, reject) { // функция-исполнитель (executor) });
Функция, переданная в конструкцию new Promise, называется исполнитель (executor). Когда Promise создаётся, она запускается автоматически. Она должна содержать «создающий» код, который когда-нибудь создаст результат.
Её аргументы resolve и reject — это колбэки, которые предоставляет сам JavaScript. Наш код — только внутри исполнителя.
Когда он получает результат, сейчас или позже — не важно, он должен вызвать один из этих колбэков:
resolve(value)
— если работа завершилась успешно, с результатом value.reject(error)
— если произошла ошибка, error — объект ошибки.Обычно исполнитель делает что-то асинхронное и после этого вызывает resolve
/reject
, то есть через какое-то время. Но это не обязательно, resolve
или reject
могут быть вызваны сразу. Например:
const promise = new Promise((resolve, reject) => { if (jobDone) { resolve('Операция выполнена'); } else { reject('Произошла ошибка'); } });
У объекта promise
, возвращаемого конструктором new Promise
, есть внутренние свойства:
Ниже пример конструктора Promise
и простого исполнителя с кодом, дающим результат с задержкой (через setTimeout
):
const promise = new Promise(function(resolve, reject) { // эта функция выполнится автоматически при вызове new Promise // через 1 секунду сигнализировать, что задача выполнена со случайным результатом setTimeout(() => resolve(Math.random()), 1000); });
Исполнитель должен вызвать что-то одно: resolve
или reject
. Состояние промиса может быть изменено только один раз. Все последующие вызовы resolve
и reject
будут проигнорированы.
const promise = new Promise((resolve, reject) => { resolve('Promise resolved'); // Промис выполнен reject('Promise rejected'); // Игнорируется. Промис уже не может быть отклонен });
Объект Promise
служит связующим звеном между исполнителем и функциями-потребителями, которые получат либо результат, либо ошибку. Функции-потребители могут быть зарегистрированы (подписаны) с помощью методов .then
, .catch
и .finally
.
Наиболее важный и фундаментальный метод — .then
.
Синтаксис:
promise.then( function(result) { /* обработает успешное выполнение */ }, function(error) { /* обработает ошибку */ } );
Первый аргумент метода .then
— функция, которая выполняется, когда промис переходит в состояние «выполнен успешно», и получает результат.
Второй аргумент .then
— функция, которая выполняется, когда промис переходит в состояние «выполнен с ошибкой», и получает ошибку.
Например, вот реакция на успешно выполненный промис:
const promise = new Promise(function(resolve, reject) { setTimeout(() => resolve("Выполнено"), 1000); }); // resolve запустит первую функцию, переданную в .then promise.then( result => alert(result), // выведет "Выполнено" через одну секунду error => alert(error) // не будет запущена );
Выполнилась первая функция.
В случае ошибки в промисе выполнится вторая:
const promise = new Promise(function(resolve, reject) { setTimeout(() => reject(new Error("Ошибка")), 1000); }); // reject запустит вторую функцию, переданную в .then promise.then( result => alert(result), // не будет запущена error => alert(error) // выведет "Error: Ошибка" спустя одну секунду );
Если мы заинтересованы только в результате успешного выполнения задачи, то в then
можно передать только одну функцию:
const promise = new Promise(resolve => { setTimeout(() => resolve("Выполнено"), 1000); }); promise.then(alert); // выведет "Выполнено" спустя одну секунду
Если мы хотели бы только обработать ошибку, то можно использовать null
в качестве первого аргумента: .then(null, errorHandlingFunction)
. Или можно воспользоваться методом .catch(errorHandlingFunction)
, который сделает тоже самое:
const promise = new Promise((resolve, reject) => { setTimeout(() => reject(new Error("Ошибка")), 1000); }); // .catch(f) это тоже самое, что promise.then(null, f) promise.catch(alert); // выведет "Error: Ошибка" спустя одну секунду
Вызов .catch(f)
— это сокращённый, «укороченный» вариант .then(null, f)
.
Вызов .finally(f)
похож на .then(f, f)
в том смысле, что f выполнится в любом случае, когда промис завершится: успешно или с ошибкой.
finally
хорошо подходит для очистки, например, остановки индикатора загрузки, его ведь нужно остановить вне зависимости от результата.
Например:
new Promise((resolve, reject) => { /* сделать что-то, что займёт время, и после вызвать resolve/reject */ }) // выполнится, когда промис завершится, независимо от того, успешно или нет .finally(() => остановить индикатор загрузки) .then(result => показать результат, err => показать ошибку)
Существует несколько важных отличий от then:
Например, здесь результат проходит через finally
к then
:
new Promise((resolve, reject) => { setTimeout(() => resolve("Данные"), 2000) }) .finally(() => alert("Промис завершён")) .then(result => alert(result)); // <-- .then обработает результат
А здесь ошибка из промиса проходит через finally
к catch
:
new Promise((resolve, reject) => { throw new Error("Ошибка"); }) .finally(() => alert("Промис завершён")) .catch(err => alert(err)); // <-- .catch обработает объект ошибки
Это очень удобно, потому что finally
не предназначен для обработки результата промиса. Так что он просто пропускает его через себя дальше.