Промисы (Promise) — JS: Асинхронное программирование
При всех его преимуществах, асинхронный код очень сложен в анализе. Буквально несколько вложенных колбеков с параллельным выполнением операций — и все, уже практически невозможно разобраться в происходящем. Страшно представить себе большую программу, в которой асинхронно все. Тысяча-другая строк кода — и ни один человек не сможет понять его.
С самого начала разработчики понимали ограниченность подхода с колбеками, но понадобилось немало времени до того, как в JavaScript появилась альтернатива — Promises. Промисы меняют способ организации кода, не добавляя нового синтаксиса. При правильном использовании они позволяют «выпрямить» асинхронный код и сделать его предельно плоским и последовательным.
Большая часть современного JavaScript кода пишется на промисах, а колбеки уходят в прошлое. Например, разработчики Node.js внедрили промисы практически во все встроенные модули. Функции для работы с файловой системой, построенные на промисах, доступны через свойство promises модуля fs. Сравните примеры:
import fs from 'fs'; // Код на колбеках похож на лесенку fs.readFile('./first', 'utf-8', (_error1, data1) => console.log(data1); fs.readFile('./second', 'utf-8', (_error2, data2) => console.log(data2); fs.readFile('./third', 'utf-8', (_error3, data3) => console.log(data3); >); >); >); // Код на промисах практически плоский // Переименование свойства promises в fsp для краткости const promises: fsp > = fs; fsp.readFile('./first', 'utf-8') .then((data1) => console.log(data1)) .then(() => fsp.readFile('./second', 'utf-8')) .then((data2) => console.log(data2)) .then(() => fsp.readFile('./third', 'utf-8')) .then((data3) => console.log(data3));
Технически промис — это специальный объект, который отслеживает асинхронную операцию и хранит внутри себя её результат. Он возвращается всеми асинхронными функциями, построенными на промисах.
const promise = fsp.readFile(src, 'utf-8');
Очень важно понимать, что промис — это не результат асинхронной операции. Это объект, который отслеживает выполнение операции. Операция по-прежнему асинхронна и выполнится когда-нибудь потом.
const promise = fsp.readFile(src, 'utf-8'); // Файл еще не прочитан console.log(promise); // Promise < > // pending — это состояние промиса говорит о том, что операция еще в процессе
Как получить результат выполнения асинхронной операции? Снаружи — никак, это просто невозможно. Но промис можно «продолжить», используя метод then() , в который нужно передать колбек-функцию. Параметром этого колбека и будет тот самый результат асинхронной операции
// Результат чтения файла передан в колбек-функцию, переданную в then // колбек вызовется только тогда, когда выполнится чтение файла fsp.readFile(src, 'utf-8').then((content) => console.log(content));
Колбек именно передается внутрь then() , а не вызывается. Вызов делает уже сам промис тогда, когда выполнится асинхронная операция.
Независимо от содержимого колбек-функции, вызов then() всегда возвращает новый промис. А возврат колбек-функции становится доступным как параметр колбека следующего then() . Именно такая организация промисов позволяет строить цепочки без необходимости вкладывать вызовы друг в друга, тем самым избегая Callback Hell.
// Предположим, что внутри файла был текст Hexlet const promise = fsp.readFile(src, 'utf-8') // результат цепочки ВСЕГДА промис .then((content) => `go to the next then with $content>`) // игнорируем результат операции .then((text) => console.log(text)); // в этот колбек, роль которого играет лог, передается значение с предыдущего then // => go to the next then with Hexlet // Вопрос на самопроверку. // Что выведется на экран, если добавить к промису выше then(console.log)?
А что, если колбек-функция вернет не просто значение, а промис? Тогда параметром следующего then() становится результат выполнения этого промиса. Иначе промисы были бы бессмысленны. Пример:
const promise = /* тут какая-то операция */ // Из колбека возвращается промис .then(() => fsp.readFile(filePath)) // В колбек попадает результат выполнения предыдущего промиса .then((text) => console.log(text));
Использование промисов внутри любой функции автоматически делает эту функцию асинхронной. Она больше не может вызываться как обычная синхронная функция, так как в таком случае невозможно воспользоваться результатом ее работы, дождаться выполнения операции или узнать об ошибках:
// Неправильное определение export const copy = (src, dest) => fsp.readFile(src, 'utf-8') .then((content) => fsp.writeFile(dest, content)); >; // Использование // делаем что-то синхронное copy(src, dest); // делаем что-то еще
Как только в коде появляется асинхронность, код должен менять свою структуру. В случае колбеков он становится вложенным, в случае промисов весь код превращается в непрерывную цепочку промисов.
// Правильное определение export const copy = (src, dest) => return fsp.readFile(src, 'utf-8') .then((content) => fsp.writeFile(dest, content)); >; // Использование // делаем что-то синхронное copy(src, dest).then(() => // делаем что-то еще >).then(/* продолжаем */) .then(/* продолжаем */);
Неправильное использование промисов
Главное преимущество промисов перед колбеками в том, что с их помощью асинхронный код становится немного похож на синхронный. Видно цепочку вызовов, и она не растет вглубь. По крайней мере в теории. На практике же, промисы используются не всегда правильно. Посмотрите на код:
fsp.readFile('./first', 'utf-8') .then((data1) => console.log(data1); // Читаем файл и продолжаем промис от этой внутренней функции return fsp.readFile('./second', 'utf-8').then((data2) => console.log(data2); // Читаем файл и продолжаем промис от этой внутренней функции return fsp.readFile('./third', 'utf-8').then((data3) => console.log(data3); >); >); >);
Несмотря на то, что здесь используются промисы, код выглядит даже сложнее чем с колбеками. Проблема в том, как построены вызовы. Продолжение цепочки идет не от верхнего промиса, а от каждой последующей асинхронной операции. В теории промисы действительно бывают вложенными, но только там, где по-другому никак. В любой другой ситуации код должен быть плоским и простым:
// Плоская цепочка промисов fsp.readFile('./first', 'utf-8') .then((data1) => console.log(data1)) .then(() => fsp.readFile('./second', 'utf-8')) .then((data2) => console.log(data2))
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
JavaScript Promise с примерами
По умолчанию код в JavaScript выполняется последовательно (в одном потоке, синхронно). То есть таким образом, когда каждая следующая операция ждёт завершения предыдущей.
На сайте очень часто встречаются задачи, для выполнения которых требуется значительное время. Если их реализовать с помощью синхронного кода, то это может привести к тому, что страницы будут подвисать (т.е. с ними нельзя будет взаимодействовать некоторое время). Таким образом, возможно возникновение негативного пользовательского опыта при использовании такого сайта и таких сценариев конечно лучше не допускать.
Чтобы этого избежать необходимо использовать асинхронный код. Он в отличие от синхронного выполняется в фоновом режиме и не блокирует основной поток. Т.е. код, расположенный после него выполняется сразу же, не дожидаясь его завершения.
Асинхронный код в JavaScript может быть написан разными способами : с помощью обратных вызовов, promise (обещаний) и ключевых слов async/await .
В этой статье рассмотрим, как это сделать с помощью promise .
Promise (промисы, обещания) – это специальный объект и набор методов в JavaScript для удобного написания асинхронного кода.
В отличие от обратных вызовов промисы позволяют решать асинхронные задачи значительно проще без создания большого количества вложенностей одних функций в другие.
Изучение промисов начнём с рассмотрения примера из реальной жизни .
Допустим, папа обещает дать 100$, если вы сдадите завтрашний экзамен на хорошо или отлично.
В данный момент вы находитесь в состоянии ожидания, т.к. не знаете какую отметку вы получите, а следовательно, не знаете получите ли вы бонус в размере 100$.
Но как только вы получите отметку по экзамену, обещание завершится. Далее в зависимости от того успешно оно завершилось или нет будет зависеть получите ли вы 100$ или нет.
На JavaScript этот пример с помощью promise реализуется следующим образом:
JavaScript
// создадим новый промис 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() :
JavaScript
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() .
JavaScript
// сдал ли экзамен 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 – выполняется в любом случае после завершения промиса, вне зависимости от конечного состояния.
JavaScript
?/ promise - промис promise .then((value) => { // . }) .catch((error) => { // . }) .finally(() => { // . });
Пример с использованием всех трёх методов:
JavaScript
При этом then() позволяет обработать не только успех, но и ошибку. Для этого необходимо передать функцию в качестве второго аргумента.
JavaScript
// promise – промис promise.then( value => { // действия при успешном завершении промиса }, error => { // действия при завершении промиса с ошибкой } );
Второй аргумент указывать не обязательно, в этом случае он будет реагировать только на успех:
JavaScript
// promise – промис promise.then(value => { // действия при успешном завершении промиса });
Для того чтобы получить значение из промиса в методах then() и catch() как уже было отмечено выше необходимо его передать в функции resolve() и reject() .
Цепочка промисов
Цепочка промисов – это простая концепция, в основу которой положено то, что методы промисов тоже в качестве результата возвращают промис. А так как они возвращают промис, то мы для них в свою очередь тоже можем вызвать методы и т.д. Таким образом, мы можем создавать цепочку из промисов, каждый из которых выполняется только после того как завершился предыдущий.
По простому цепочка промисов позволяет очень компактно записать код для последовательного выполнения нескольких асинхронных задач, каждая из которых должна выполняется после завершения предыдущей.
Пример цепочки промисов:
JavaScript
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))
- сначала выполняется первый промис, который успешно завершится через 3 секунды со значением 2;
- после этого выполнится метод then() , который выведет в консоль значение переменной value , которое равно аргументу, переданному в resolve() и возвратит в качестве результата новый промис; он также как и предыдущий успешно завершится через 3 секунды со значением value * 2 ;
- затем выполнится следующий then() , он выполнит действия аналогично предыдущему then() ;
- дальше выполнится последний then() , который просто выведет в консоль значение параметра value .
Promice.all() и Promise.race()
Promice.all() и Promise.race() – это статические методы Promice , которые принимают на вход массив промисов и возвращают новый промис.
В случае с Promice.all() промис завершится когда завершатся все промисы в массиве.
JavaScript
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() промис завершится, как только выполнится первый промис из массива.
JavaScript
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'
Задачи
1. Асинхронный код, ниже реализованный с использованием функций обратного вызова, необходимо переписать через промисы:
JavaScript
2. Напишите функцию, с помощью которой можно будет выполнять различные действия с задержкой.
JavaScript
// функция, которую нужно реализовать const sleep = ms => { . } // её использование для выполнения действия с задержкой 5 секунд sleep(5000).then(() => { console.log('Выполнилось через 5 секунд!'); }); // её использование для выполнения действия с задержкой 10 секунд sleep(10000).then(() => { console.log('Выполнилось через 10 секунд!'); });
3. Перепишите код для динамической загрузки скриптов в определенном порядке через промисы:
JavaScript
const loadJs = (src, success, error) => { const script = document.createElement('script'); script.onload = success; script.onerror = error; script.src = src; document.head.append(script); } // код для открытия модального окна нужно выполнить после загрузке bootstrap.bundle.min.js, а bootstrap.bundle.min.js следует загрузить только после загрузки jquery.min.js loadJs('https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js', () => { loadJs('https://cdn.jsdelivr.net/npm/bootstrap@4.6.1/dist/js/bootstrap.bundle.min.js', () => { setTimeout(() => { $('.modal').modal('show'); }, 1000); }, () => { console.error('Ошибка при загрузки скриптов!'); } ) }, () => { console.error('Ошибка при загрузки скриптов!'); } )
Разбираемся с промисами в JavaScript
Доброго времени суток, Хабр! Представляю вашему вниманию перевод статьи «Understanding Promises in JavaScript» автора Sukhjinder Arora.

От автора перевода: Так же, как и сам автор, я надеюсь, что статья оказалась для вас полезной. Пожалуйста, если она и вправду помогла вам узнать для себя что-то новое, то не поленитесь зайти на оригинал статьи и поблагодарить автора! Буду рад вашему фидбеку!
JavaScript — это однопоточный язык программирования, это означает, что за раз может быть выполнено что-то одно. До ES6 мы использовали обратные вызовы, чтобы управлять асинхронными задачами, такими как сетевой запрос.
Используя промисы, мы можем избегать “ад обратных вызовов” и сделать наш код чище, более читабельным и более простым для понимания.
Предположим, что мы хотим асинхронно получить некоторые данные с сервера, используя обратные вызовы мы сделали бы что-то вроде этого:
getData(function(x)< console.log(x); getMoreData(x, function(y)< console.log(y); getSomeMoreData(y, function(z)< console.log(z); >); >); >);
Здесь я запрашиваю некоторые данные с сервера при помощи функции getData(), которая получает данные внутри функции обратного вызова. Внутри функции обратного вызова я запрашиваю дополнительные данные при помощи вызова функции getMoreData(), передавая предыдущие данные как аргумент и так далее.
Это то, что мы называем “адом обратных вызовов”, где каждый обратный вызов вложен внутрь другого, и каждый внутренний обратный вызов зависит от его родителя.
Мы можем переписать приведенный выше фрагмент используя промисы:
getData() .then((x) => < console.log(x); return getMoreData(x); >) .then((y) => < console.log(y); return getSomeMoreData(y); >) .then((z) => < console.log(z); >);
Вы можете видеть, что стало более читабельно, чем в случае первого примера с обратными вызовами.
Что такое Промисы?
Промис(Обещание) — это объект который содержит будущее значение асинхронной операции. Например, если вы запрашиваете некоторые данные с сервера, промис обещает нам получить эти данные, которые мы сможем использовать в будущем.
Прежде чем погрузиться во все эти технические штуки, давайте разберемся с терминологией промисов.
Состояния промисов
Промис в JavaScript, как и обещание в реальной жизни, имеет 3 состояния. Это может быть 1) нерешенный(в ожидании), 2) решенный/resolved (выполненный) или 3) отклоненный/rejected.

Нерешенный или Ожидающий — Промис ожидает, если результат не готов. То есть, ожидает завершение чего-либо(например, завершения асинхронной операции).
Решенный или Выполненный — Промис решен, если результат доступен. То есть, что-то завершило свое выполнение(например, асинхронная операция) и все прошло хорошо.
Отклоненный — Промиc отклонен, если произошла ошибка в процессе выполнения.
Теперь мы знаем, что такое Промис и его терминологию, давайте вернемся назад к практической части промисов.
Создаем Промис
В большинстве случаев вы будете просто использовать промисы, а не создавать их, но все же важно знать как они создаются.
const promise = new Promise((resolve, reject) => < . >);
Мы создали новый промис, используя конструктор Промисов, он принимает один аргумент, обратный вызов, также известный как исполнительная функция, которая принимает 2 обратных вызова, resolve и reject.
Исполнительная функция выполняется сразу же после создания промиса. Промис становится выполненным при помощи вызова resolve(), а отклоненным при помощи reject(). Например:
const promise = new Promise((resolve, reject) => < if(allWentWell) < resolve('Все прошло отлично!'); >else < reject('Что-то пошло не так'); >>);
resolve() и reject() принимают один аргумент, который может быть строкой, числом, логическим выражением, массивом или объектом.
Давайте взглянем на другой пример, чтобы полностью понять как создаются промисы.
const promise = new Promise((resolve, reject) => < const randomNumber = Math.random(); setTimeout(() => < if(randomNumber < .6) < resolve('Все прошло отлично!'); >else < reject('Что-то пошло не так'); >>, 2000); >);
Здесь я создал новый промис используя конструктор Промисов. Промис выполняется или отклоняется через 2 секунды после его создания. Промис выполняется, если randomNumber меньше, чем .6 и отклоняется в остальных случаях.
Когда промис был создан, он будет в состоянии ожидания и его значение будет undefined.

После 2 секунд таймер заканчивается, промис случайным образом либо выполняется, либо отклоняется, и его значением будет то, которое передано в функцию resolve или reject. Ниже пример двух случаев:


Примечание: Промис может быть выполнен или отклонен только один раз. Дальнейшие вызовы resolve() или reject() никак не повлияют на состояние промиса. Пример:
const promise = new Promise((resolve, reject) => < resolve('Promise resolved'); // Промис выполнен reject('Promise rejected'); // Промис уже не может быть отклонен >);
Так как resolve() была вызвана первой, то промис теперь получается статус “выполненный”. Последующий вызов reject() никак не повлияет на состояние промиса.
Использование Промиса
Теперь мы знаем как создавать промисы, давайте теперь разберемся как применять уже созданный промис. Мы используем промисы при помощи методов then() и catch().
Например, запрос данных из API при помощи fetch, которая возвращает промис.
.then() синтаксис: promise.then(successCallback, failureCallback)
successCallback вызывается, если промис был успешно выполнен. Принимает один аргумент, который является значением переданным в resolve().
failureCallback вызывается, если промис был отклонен. Принимает один аргумент, который является значением преданным в reject().
const promise = new Promise((resolve, reject) => < const randomNumber = Math.random(); if(randomNumber < .7) < resolve('Все прошло отлично!'); >else < reject(new Error('Что-то пошло не так')); >>); promise.then((data) => < console.log(data); // вывести 'Все прошло отлично!' >, (error) => < console.log(error); // вывести ошибку >);
Если промис был выполнен, то вызывается successCallback со значением, переданным в resolve(). И если промис был отклонен, то вызывается failureCallback со значением, переданным в reject().
.catch() синтаксис: promise.catch(failureCallback)
Мы используем catch() для обработки ошибок. Это более читабельно, нежели обработка ошибок внутри failureCallback внутри обратного вызова метода then().
const promise = new Promise((resolve, reject) => < reject(new Error('Что-то пошло не так')); >); promise .then((data) => < console.log(data); >) .catch((error) => < console.log(error); // вывести ошибку >);
Цепочка промисов
Методы then() и catch() также могут возвращать новый промис, который может быть обработан цепочкой других then() в конце предыдущего метода then().
Мы используем цепочку промисов, когда хотим выполнить последовательность промисов.
const promise1 = new Promise((resolve, reject) => < resolve('Promise1 выполнен'); >); const promise2 = new Promise((resolve, reject) => < resolve('Promise2 выполнен'); >); const promise3 = new Promise((resolve, reject) => < reject('Promise3 отклонен'); >); promise1 .then((data) => < console.log(data); // Promise1 выполнен return promise2; >) .then((data) => < console.log(data); // Promise2 выполнен return promise3; >) .then((data) => < console.log(data); >) .catch((error) => < console.log(error); // Promise3 отклонен >);
И так, что тут происходит?
Когда promise1 выполнен, вызывается метод then(), который возвращает promise2.
Далее, когда выполнен promise2, снова вызывается then() и возвращает promise3.
Так как promise3 отклонен, вместо следующего then(), вызывается catch(), который и обрабатывает отклонение promise3.
Примечание: Как правило достаточно одного метода catch() для обработки отклонения любого из промисов в цепочке, если этот метод находится в конце неё.
Распространенная ошибка
Достаточно много новичков делают ошибку, вкладывая одни промисы внутрь других. Например:
const promise1 = new Promise((resolve, reject) => < resolve('Promise1 выполнен'); >); const promise2 = new Promise((resolve, reject) => < resolve('Promise2 выполнен'); >); const promise3 = new Promise((resolve, reject) => < reject('Promise3 отклонен'); >); promise1.then((data) => < console.log(data); // Promise1 выполнен promise2.then((data) => < console.log(data); // Promise2 выполнен promise3.then((data) =>< console.log(data); >).catch((error) => < console.log(error); // Promise3 отклонен >); >).catch((error) => < console.log(error); >) >).catch((error) => < console.log(error); >);
Несмотря на то, что это будет работать нормально, это считается плохим стилем и делает код менее читабельным. Если у вас есть последовательность промисов для выполнения, будет лучше ставить их один за другим, нежели вкладывать один внутрь другого.
Promise.all( )
Этот метод берет массив промисов и возвращает новый промис, который будет выполненным, когда все промисы внутри массива выполнены или отклонен, как только встречается промис, который отклоняется. Например:
const promise1 = new Promise((resolve, reject) => < setTimeout(() =>< resolve('Promise1 выполнен'); >, 2000); >); const promise2 = new Promise((resolve, reject) => < setTimeout(() =>< resolve('Promise2 выполнен'); >, 1500); >); Promise.all([promise1, promise2]) .then((data) => console.log(data[0], data[1])) .catch((error) => console.log(error));
Здесь аргументом внутри then() выступает массив, который содержит значения промисов в том же порядке, в котором они передавались в Promise.all().(Только в том случае, если все промисы выполняются)
Промис отклоняется с причиной отклонения первого промиса в переданном массиве. Например:
const promise1 = new Promise((resolve, reject) => < setTimeout(() =>< resolve('Promise1 выполнен'); >, 2000); >); const promise2 = new Promise((resolve, reject) => < setTimeout(() =>< reject('Promise2 отклонен'); >, 1500); >); Promise.all([promise1, promise2]) .then((data) => console.log(data[0], data[1])) .catch((error) => console.log(error)); // Promise2 отклонен
Здесь у нас есть два промиса, где один выполняется через 2 секунды, а другой отклоняется через 1.5 секунды. Как только второй промис отклоняется, возвращенный от Promise.all() промис отклоняется не дожидаясь выполнения первого.
Этот метод может быть полезен, когда у вас есть более одного промиса и вы хотите знать, когда все промисы выполнены. Например, если вы запрашиваете данные из стороннего API и вы хотите что-то сделать с этими данными только тогда, когда все запросы проходят успешно.
По итогу мы имеем Promise.all(), который ждет успешное выполнение всех промисов, либо завершает свое выполнение при обнаружении первой неудачи в массиве промисов.
Promise.race( )
Этот метод принимает массив промисов и возвращает один новый промис, который будет выполненным, как только встретится выполненный промис в массиве или же отклоняется, если отклоненный промис встречается раньше. Например:
const promise1 = new Promise((resolve, reject) => < setTimeout(() =>< resolve('Promise1 выполнен'); >, 1000); >); const promise2 = new Promise((resolve, reject) => < setTimeout(() =>< reject('Promise2 отклонен'); >, 1500); >); Promise.race([promise1, promise2]) .then((data) => console.log(data)) // Promise1 выполнен .catch((error) => console.log(error));
Тут мы имеем два промиса, где один выполняется через 1 секунду, а другой отклоняется через 1.5 секунды. Как только первый промис выполнен, возвращенный из Promise.race() промис будет иметь статус выполненного не дожидаясь статуса второго промиса.
Здесь data, которая передается в then() является значением первого, выполненного, промиса.
По итогу, Promise.race() дожидается первого промиса и берет его статус как статус возвращаемого промиса.
Комментарий автора перевода: Отсюда собственно и название. Race — гонка
Заключение
Мы узнали, что такое промисы и с чем их едят в JavaScript. Промисы состоят из двух частей 1) Создание промиса и 2) Использование промиса. Большую часть времени вы будете пользоваться промисами, нежели создавать их, но важно знать как они создаются.
Вот и все, надеюсь эта статья оказалась для вас полезной!
- javascript
- перевод
- веб-разработка
- разработка веб-сайтов
- промисы
Promise
При стандартном выполнении JavaScript инструкции выполняются последовательно, одна за другой. То есть сначала выполняется первая инструкция, потом вторая и так далее. Однако что, если одна из этих операций выполняется продолжительное время. Например, она выполняет какую-то высоконагруженную работу, как обращение по сети или обращение к базе данных, что может занять неопределенное и иногда продолжительное время. В итоге при последовательном выполнении все последующие операции будут ожидать выполнения этой операции. Чтобы избежать подобной ситуации JavaScript предоставляет ряд инструментов, которые позволяют избежать подобного сценария, чтобы последующие операции могли выполняться, пока выполняется продолжительная операция. Одним из таким инструментов являются промисы (promise).
Промис (promise) — это объект, представляющий результат успешного или неудачного завершения асинхронной операции. Асинхронная операция, упрощенно говоря, это некоторое действие, выполняется независимо от окружающего ее кода, в котором она вызывается, не блокирует выполнение вызываемого кода.
Промис может находиться в одном из следующих состояний:
- pending (состояние ожидания): начальное состояние, промис создан, но выполнение еще не завершено
- fulfilled (успешно завершено): действие, которое представляет промис, успешно завершено
- rejected (завершено с ошибкой): при выполнении действия, которое представляет промис, произошла ошибка
Для создания промиса применяется конструктор типа Promise :
new Promise(executor)
В качестве параметра конструктор принимает функцию, которая выполняется при создании промиса. Обычно эта функция представляет асинхронные операции, которые занимают продолжительное время. Например, определим простейший промис:
const myPromise = new Promise(function()< console.log("Выполнение асинхронной операции"); >);
Здесь функция просто выводит на консоль сообщение. Соответственно при выполнении этого кода мы увидим на консоли сообщение «Выполнение асинхронной операции» .
При создании промиса, когда его функция еще не начала выполняться, промис переходит в состояние «pending», то есть ожидает выполнения.
Как правило, функция, которая передается в конструктор 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() .
