Promise — JavaScript
Содержание
В этой статье разберём что такое промисы, как они работают, зачем они нужны, а также различные примеры их использования.
Что такое promise?
По умолчанию код в JavaScript выполняется последовательно (в одном потоке, синхронно). То есть таким образом, когда каждая следующая операция ждёт завершения предыдущей.
На сайте очень часто встречаются задачи, для выполнения которых требуется значительное время. Если их реализовать с помощью синхронного кода, то это может привести к тому, что страницы будут подвисать (т.е. с ними нельзя будет взаимодействовать некоторое время). Таким образом, возможно возникновение негативного пользовательского опыта при использовании такого сайта и таких сценариев конечно лучше не допускать.
Чтобы этого избежать необходимо использовать асинхронный код. Он в отличие от синхронного выполняется в фоновом режиме и не блокирует основной поток. Т.е. код, расположенный после него выполняется сразу же, не дожидаясь его завершения.
Асинхронный код в JavaScript может быть написан разными способами: с помощью обратных вызовов, promise (обещаний) и ключевых слов async/await
.
В этой статье рассмотрим, как это сделать с помощью promise.
Promise (промисы, обещания) – это специальный объект и набор методов в JavaScript для удобного написания асинхронного кода.
В отличие от обратных вызовов промисы позволяют решать асинхронные задачи значительно проще без создания большого количества вложенностей одних функций в другие.
Изучение промисов начнём с рассмотрения примера из реальной жизни.
Допустим, папа обещает дать 100$, если сдадите завтрашний экзамен на хорошо или отлично.
В данный момент Вы находитесь в состоянии ожидания, т.к. не знаете какую отметку получите, а следовательно, не знаете получите ли бонус в размере 100$.
Но как только Вы получите отметку по экзамену, обещание завершится. Далее в зависимости от того успешно оно завершилось или нет будет зависеть получите ли Вы 100$ или нет.
На JavaScript этот пример с помощью promise реализуется следующим образом:
// создадим новый промис const promise = new Promise((resolve, reject) => { // оценку, которые мы получим определим случайным образом спустя некоторое время (например, 5 секунд) setTimeout(() => { // сгенерируем оценку от 2 до 5 const mark = Math.floor(Math.random() * 4) + 2; // если оценка больше 3, то... if (mark > 3) { // завершим промис успешно: для этого вызовем функцию resolve() и передадим ей в скобках полученную оценку (это нужно для того, чтобы мы затем её могли получить в методе then()) resolve(mark) } // завершим промис с ошибкой reject(mark) }, 5000) }); // выполнение действий после завершения промиса выполняется с помощью методов: then (в случае успеха) и catch (при ошибке) promise .then(result => console.log(`Ура! Я сдал экзамен на ${result}! Папа, как и обещал дал мне 100$.`)) .catch(result => console.log(`Увы, я получил оценку ${result}! Папа мне не дал 100$`))
Теперь разберём как всё это работает, и начнём с создания промиса.
Создание промиса
Начинается процесс написания промиса с его создания. Осуществляется это с помощью конструктора, т.е. с new Promise()
:
const promise = new Promise((resolve, reject) => { // асинхронный код });
Конструктор промиса принимает 2 аргумента, которые являются функциями. Первый аргумент называют resolve
, а второй – reject
. Внутрь промиса помещают асинхронный код, можно конечно и синхронный, но тогда в этом не будет смысла.
Промис завершает своё выполнение, когда вызывается функция resolve()
или reject()
.
Функцию resolve()
вызывают обычно в том месте кода, в котором асинхронная операция должна завершиться успешно. А функцию reject()
– там, где она должна завершиться с ошибкой.
Состояния промиса
Промис начинается с состояния ожидания (state: "pending"
). Оно говорит о том, что он ещё не выполнен (результат undefined
).
Промис завершается после вызова resolve()
или reject()
. При этом его состояние переходит соответственно в выполнено (state: "fulfilled"
) или отклонено (state: "rejected"
).
Внутрь функций resolve()
или reject()
можно поместить аргумент, который затем будет доступен соответственно в then()
или catch()
.
// сдал ли экзамен const passexam = true; // промис const result = new Promise((resolve, reject) => { setTimeout(() => { passexam ? resolve('Папа подарил 100$.') : reject('Папа не подарил 100$.'); }, 5000); }); result .then(value => { console.log(result); console.log(value); }) .catch(value => { console.log(result); console.error(value); });
Когда passexam
равен true
, промис успешно завершится через 5 секунд посредством вызова функции resolve()
. Так как промис завершился успешно, то будет вызван метод then()
.
Если значение константы passexam
поменять на false
, то промис завершится с ошибкой через 5 секунд с помощью вызова функции reject()
. Так как в этом случае промис завершился с ошибкой, то, следовательно, будет вызван catch()
.
Методы промисов
У каждого промиса есть определённый набор методов, которые мы можем использовать:
then
– выполняется, когда промис завершился успешно (после вызова функцииresolve()
)catch
– вызывается, если промис завершается ошибкой (после вызоваreject()
)finally
– выполняется в любом случае после завершения промиса, вне зависимости от конечного состояния.
promise .then((value) => { // ... }) .catch((error) => { // ... }) .finally(() => { // ... });
Пример с использованием всех трёх методов:
<div><button id="run">Новая попытка</button></div> <div id="result"></div>
let isProcess = false elResult = document.querySelector('#result') document.querySelector('#run').onclick = () => { if (isProcess) { elResult.textContent = 'Подождите! Задача ещё выполняется!' return; } isProcess = true; elResult.textContent = 'Задача в процессе...'; const promise = new Promise((resolve, reject) => { setTimeout(() => { const mark = Math.floor(Math.random() * 4) + 2 mark > 3 ? resolve(mark) : reject(mark) }, 5000); }) promise .then(value => { elResult.textContent = `Ура! Вы сдали экзамен на ${value}! Папа, как и обещал дал вам 100$.` }) .catch(value => { elResult.textContent = `Увы, вы получили оценку ${value}! Папа не дал вам 100$` }) .finally(() => { isProcess = false }) }
При этом then()
позволяет обработать не только успех, но и ошибку. Для этого необходимо передать функцию в качестве второго аргумента.
promise.then( value => { // действия при успешном завершении промиса }, error => { // действия при завершении промиса с ошибкой } );
Второй аргумент указывать не обязательно, в этом случае он будет реагировать только на успех:
promise.then(value => { // действия при успешном завершении промиса });
Для того чтобы получить значение из промиса в методах then()
и catch()
как уже было отмечено выше необходимо его передать в функции resolve()
и reject()
.
Цепочка промисов
Цепочка промисов – это простая концепция, в основу которой положено то, что методы промисов тоже в качестве результата возвращают промис. Так как они возвращают промис, то мы для них в свою очередь тоже можем вызвать методы.
Таким образом, мы можем создавать цепочку из промисов, каждый из которых выполняется только после того как завершился предыдущий.
По простому цепочка промисов позволяет очень компактно записать код для последовательного выполнения нескольких асинхронных задач, каждая из которых должна выполняется после завершения предыдущей.
Пример цепочки промисов:
const promise = new Promise(resolve => { setTimeout(() => resolve(2), 3000); }); promise .then(value => { console.log(value); return new Promise(resolve => { setTimeout(() => resolve(value * 2), 3000); }); }) .then(value => { console.log(value); return new Promise(resolve => { setTimeout(() => resolve(value * 2), 3000); }); }) .then(value => console.log(value))
В этом примере:
- Cначала выполняется первый промис, который успешно завершится через 3 секунды со значением 2.
- После этого выполнится метод
then()
, который выведет в консоль значение переменнойvalue
, которое равно аргументу, переданному вresolve()
и возвратит в качестве результата новый промис, он также как и предыдущий успешно завершится через 3 секунды со значениемvalue * 2
. - Затем выполнится следующий
then()
, он выполнит действия аналогично предыдущемуthen()
. - Дальше выполнится последний
then()
, который просто выведет в консоль значение параметраvalue
.
Promise.all() и Promise.race()
Promise.all()
и Promise.race()
– это статические методы Promise
, которые принимают на вход массив промисов и возвращают новый промис.
В случае с Promise.all()
промис завершится когда завершатся все промисы в массиве.
const promise1 = new Promise(resolve => setTimeout(resolve, 1000, 'one')) const promise2 = new Promise(resolve => setTimeout(resolve, 2000, 'two')) const promise3 = new Promise(resolve => setTimeout(resolve, 3000, 'three')) Promise.all([promise1, promise2, promise3]).then(value => console.log(value)) // ['one', 'two', 'three']
При использовании Promise.race()
промис завершится, как только выполнится первый промис из массива.
const promise1 = new Promise(resolve => setTimeout(resolve, 1000, 'one')) const promise2 = new Promise(resolve => setTimeout(resolve, 2000, 'two')) const promise3 = new Promise(resolve => setTimeout(resolve, 3000, 'three')) Promise.race([promise1, promise2, promise3]).then(value => console.log(value)) // 'one'
Источник: itchief.ru