- Операторы
- Управляющие инструкции
- JS Объекты
- браузер BOM
- HTML DOM
- События
- HTML Объекты
- Промисы, async/await
- Колбэки
- Промисы
- Цепочка промисов
- Обработка ошибок
- Promise API
- Промисификация
- Микрозадачи
- Async/await
- Сетевые запросы
- Бинарные данные и файлы
- Разное
Promise API
В классе Promise
есть 5 статических методов. Давайте познакомимся с ними.
Promise.all
Допустим, нам нужно запустить множество промисов параллельно и дождаться, пока все они выполнятся.
Например, параллельно загрузить несколько файлов и обработать результат, когда он готов.
Для этого как раз и пригодится Promise.all
.
Синтаксис:
let promise = Promise.all([...промисы...]);
Метод Promise.all
принимает массив промисов (может принимать любой перебираемый объект, но обычно используется массив) и возвращает новый промис.
Новый промис завершится, когда завершится весь переданный список промисов, и его результатом будет массив их результатов.
Например, Promise.all
, представленный ниже, выполнится спустя 3 секунды, его результатом будет массив [1, 2, 3]
:
Promise.all([ new Promise(resolve => setTimeout(() => resolve(1), 3000)), // 1 new Promise(resolve => setTimeout(() => resolve(2), 2000)), // 2 new Promise(resolve => setTimeout(() => resolve(3), 1000)) // 3 ]).then(alert); // когда все промисы выполнятся, результат будет 1,2,3 // каждый промис даёт элемент массива
Обратите внимание, что порядок элементов массива в точности соответствует порядку исходных промисов. Даже если первый промис будет выполняться дольше всех, его результат всё равно будет первым в массиве.
Часто применяемый трюк – пропустить массив данных через map-функцию,
которая для каждого элемента создаст задачу-промис, и затем обернёт
получившийся массив в Promise.all
.
Например, если у нас есть массив ссылок, то мы можем загрузить их вот так:
let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://api.github.com/users/jeresig' ]; // Преобразуем каждый URL в промис, возвращённый fetch let requests = urls.map(url => fetch(url)); // Promise.all будет ожидать выполнения всех промисов Promise.all(requests) .then(responses => responses.forEach( response => alert(`${response.url}: ${response.status}`) ));
А вот пример побольше, с получением информации о пользователях GitHub по их логинам из массива (мы могли бы получать массив товаров по их идентификаторам, логика та же):
let names = ['iliakan', 'remy', 'jeresig']; let requests = names.map(name => fetch(`https://api.github.com/users/${name}`)); Promise.all(requests) .then(responses => { // все промисы успешно завершены for(let response of responses) { alert(`${response.url}: ${response.status}`); // покажет 200 для каждой ссылки } return responses; }) // преобразовать массив ответов response в response.json(), // чтобы прочитать содержимое каждого .then(responses => Promise.all(responses.map(r => r.json()))) // все JSON-ответы обработаны, users - массив с результатами .then(users => users.forEach(user => alert(user.name)));
Если любой из промисов завершится с ошибкой, то промис, возвращённый Promise.all
, немедленно завершается с этой ошибкой.
Например:
Promise.all([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).catch(alert); // Error: Ошибка!
Здесь второй промис завершится с ошибкой через 2 секунды. Это приведёт к немедленной ошибке в Promise.all
, так что выполнится .catch
: ошибка этого промиса становится ошибкой всего Promise.all
.
В случае ошибки, остальные результаты игнорируются
Если один промис завершается с ошибкой, то весь Promise.all
завершается с ней, полностью забывая про остальные промисы в списке. Их результаты игнорируются.
Например, если сделано несколько вызовов fetch
, как в примере выше, и один не прошёл, то остальные будут всё ещё выполняться, но Promise.all
за ними уже не смотрит. Скорее всего, они так или иначе завершатся, но их результаты будут проигнорированы.
Promise.all
ничего не делает для их отмены, так как в промисах вообще нет концепции «отмены». В главе Fetch: прерывание запроса мы рассмотрим AbortController
, который помогает с этим, но он не является частью Promise API.
Promise.all(iterable)
разрешает передавать не-промисы в iterable
(перебираемом объекте)
Обычно, Promise.all(...)
принимает перебираемый объект промисов (чаще всего массив). Но если
любой из этих объектов не является промисом, он передаётся в итоговый
массив «как есть».
Например, здесь результат: [1, 2, 3]
Promise.all([ new Promise((resolve, reject) => { setTimeout(() => resolve(1), 1000) }), 2, 3 ]).then(alert); // 1, 2, 3
Таким образом, мы можем передавать уже готовые значения, которые не являются промисами, в Promise.all
, иногда это бывает удобно.
Promise.allSettled
Эта возможность была добавлена в язык недавно.
В старых браузерах может понадобиться полифил.
Promise.all
завершается с ошибкой, если она возникает в
любом из переданных промисов. Это подходит для ситуаций «всё или
ничего», когда нам нужны все результаты для продолжения:
Promise.all([ fetch('/template.html'), fetch('/style.css'), fetch('/data.json') ]).then(render); // методу render нужны результаты всех fetch
Метод Promise.allSettled
всегда ждёт завершения всех промисов. В массиве результатов будет
{status:"fulfilled", value:результат}
для успешных завершений,{status:"rejected", reason:ошибка}
для ошибок.
Например, мы хотели бы загрузить информацию о множестве пользователей. Даже если в каком-то запросе ошибка, нас всё равно интересуют остальные.
Используем для этого Promise.allSettled
:
let urls = [ 'https://api.github.com/users/iliakan', 'https://api.github.com/users/remy', 'https://no-such-url' ]; Promise.allSettled(urls.map(url => fetch(url))) .then(results => { // (*) results.forEach((result, num) => { if (result.status == "fulfilled") { alert(`${urls[num]}: ${result.value.status}`); } if (result.status == "rejected") { alert(`${urls[num]}: ${result.reason}`); } }); });
Массив results
в строке (*)
будет таким:
[ {status: 'fulfilled', value: ...объект ответа...}, {status: 'fulfilled', value: ...объект ответа...}, {status: 'rejected', reason: ...объект ошибки...} ]
То есть, для каждого промиса у нас есть его статус и значение/ошибка.
Полифил
Если браузер не поддерживает Promise.allSettled
, для него легко сделать полифил:
if(!Promise.allSettled) { Promise.allSettled = function(promises) { return Promise.all(promises.map(p => Promise.resolve(p).then(value => ({ status: 'fulfilled', value: value }), error => ({ status: 'rejected', reason: error })))); }; }
В этом коде promises.map
берёт аргументы, превращает их в промисы (на всякий случай) и добавляет каждому обработчик .then
.
Этот обработчик превращает успешный результат value
в {state:'fulfilled', value: value}
, а ошибку error
в {state:'rejected', reason: error}
. Это как раз и есть формат результатов Promise.allSettled
.
Затем мы можем использовать Promise.allSettled
, чтобы получить результаты всех промисов, даже если при выполнении какого-то возникнет ошибка.
Promise.race
Метод очень похож на Promise.all
, но ждёт только первый выполненный промис, из которого берёт результат (или ошибку).
Синтаксис:
let promise = Promise.race(iterable);
Например, тут результат будет 1
:
Promise.race([ new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)), new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ошибка!")), 2000)), new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)) ]).then(alert); // 1
Быстрее всех выполнился первый промис, он и дал результат. После этого остальные промисы игнорируются.
Promise.resolve/reject
Методы Promise.resolve
и Promise.reject
редко используются в современном коде, так как синтаксис async/await
(мы рассмотрим его чуть позже) делает его, в общем-то, не нужным.
Мы рассмотрим их здесь для полноты картины, а также для тех, кто по каким-то причинам не может использовать async/await
.
Promise.resolve(value)
создаёт успешно выполненный промис с результатомvalue
.
То же самое, что:
let promise = new Promise(resolve => resolve(value));
Этот метод используют для совместимости: когда ожидается, что функция возвратит именно промис.
Например, функция loadCached
ниже загружает URL и
запоминает (кеширует) его содержимое. При будущих вызовах с тем же URL
он тут же читает предыдущее содержимое из кеша, но использует Promise.resolve
, чтобы сделать из него промис, для того, чтобы возвращаемое значение всегда было промисом:
let cache = new Map(); function loadCached(url) { if (cache.has(url)) { return Promise.resolve(cache.get(url)); // (*) } return fetch(url) .then(response => response.text()) .then(text => { cache.set(url,text); return text; }); }
Мы можем писать loadCached(url).then(…)
, потому что функция loadCached
всегда возвращает промис. Мы всегда можем использовать .then
после loadCached
. Это и есть цель использования Promise.resolve
в строке (*)
.
Promise.reject
Promise.reject(error)
создаёт промис, завершённый с ошибкойerror
.
То же самое, что:
let promise = new Promise((resolve, reject) => reject(error));
На практике этот метод почти никогда не используется.
Итого
Мы ознакомились с пятью статическими методами класса Promise
:
Promise.all(promises)
– ожидает выполнения всех промисов и возвращает массив с результатами. Если любой из указанных промисов вернёт ошибку, то результатом работыPromise.all
будет эта ошибка, результаты остальных промисов будут игнорироваться.Promise.allSettled(promises)
(добавлен недавно) – ждёт, пока все промисы завершатся и возвращает их результаты в виде массива с объектами, у каждого объекта два свойства:state
:"fulfilled"
, если выполнен успешно или"rejected"
, если ошибка,value
– результат, если успешно илиreason
– ошибка, если нет.
Promise.race(promises)
– ожидает первый выполненный промис, который становится его результатом, остальные игнорируются.Promise.resolve(value)
– возвращает успешно выполнившийся промис с результатомvalue
.Promise.reject(error)
– возвращает промис с ошибкойerror
.
Из всех перечисленных методов, самый часто используемый – это, пожалуй, Promise.all
.
Источник: learn.javascript.ru/promise-api