В этом руководстве вы узнаете, как использовать статический метод JavaScript Promise.race()
.
Статический метод Promise.race()
принимает список обещаний как итерируемый объект и возвращает новое обещание, которое выполняется или отклоняется, как только появляется одно обещание, которое выполняется или отклоняется, со значением или причиной этого обещания.
Синтаксис:
Promise.race(iterable)
В этом синтаксисе iterable
— это итерируемый объект, содержащий список промисов.
Название Promise.race()
подразумевает, что все промисы соревнуются друг с другом с одним победителем, либо разрешенным, либо отклоненным.
Следующая схема:
На этой диаграмме:
promise1
выполняется со значениемv1
в момент времениt1
.promise2
отклоняется сerror
в момент времениt2
.- Поскольку
promise1
разрешается раньше, чемpromise2
,promise1
выигрывает гонку. ПоэтомуPromise.race([promise1, promise2])
возвращает новое обещание, которое выполняется со значениемv1
в момент времениt1
.
Смотрите другую схему:
На этой диаграмме:
promise1
выполняется сv1
в момент времениt2
promise2
отклонено сerror
вt1
.- Поскольку
promise2
разрешается раньше, чемpromise1
,promise2
выигрывает гонку. ПоэтомуPromise.race([promise1, promise2])
возвращает новое обещание, которое отклонено сerror
в момент времениt1
.
Примеры
Давайте рассмотрим несколько примеров использования статического метода Promise.race()
.
1) Простые примеры
Следующее создает два обещания: одно разрешается за 1 секунду, а другое разрешается за 2 секунды. Поскольку первое обещание выполняется быстрее, чем второе, Promise.race()
разрешается со значением из первого обещания:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('The first promise has resolved'); resolve(10); }, 1 * 1000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('The second promise has resolved'); resolve(20); }, 2 * 1000); }); Promise.race([p1, p2]) .then(value => console.log(`Resolved: ${value}`)) .catch(reason => console.log(`Rejected: ${reason}`));
Выход:
The first promise has resolved Resolved: 10 The second promise has resolved
В следующем примере создаются два обещания. Первое обещание разрешается за 1 секунду, а второе отклоняется за 2 секунды. Поскольку первый промис быстрее второго, возвращаемый промис разрешается в значение из первого промиса:
const p1 = new Promise((resolve, reject) => { setTimeout(() => { console.log('The first promise has resolved'); resolve(10); }, 1 * 1000); }); const p2 = new Promise((resolve, reject) => { setTimeout(() => { console.log('The second promise has rejected'); reject(20); }, 2 * 1000); }); Promise.race([p1, p2]) .then(value => console.log(`Resolved: ${value}`)) .catch(reason => console.log(`Rejected: ${reason}`));
Выход:
The first promise has resolved Resolved: 10 The second promise has rejected
Обратите внимание, что если бы второе обещание было быстрее, чем первое, обещание возврата было бы отклонено по причине второго обещания.
2) Практический пример
Предположим, вам нужно показать счетчик, если процесс загрузки данных с сервера занимает больше нескольких секунд.
Для этого вы можете использовать статический метод Promise.race()
. Если время ожидания истекло, вы показываете индикатор загрузки, в противном случае вы показываете сообщение.
Ниже показан код HTML:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>JavaScript Promise.race() Demo</title> <link href="css/promise-race.css" rel="stylesheet"> </head> <body> <div id="container"> <button id="btnGet">Get Message</button> <div id="message"></div> <div id="loader"></div> </div> <script src="js/promise-race.js"></script> </body> </html>
Для создания индикатора загрузки мы используем функцию анимации CSS. Дополнительную информацию см. в promise-race.css
. С технической точки зрения, если элемент имеет класс .loader
, он показывает индикатор загрузки.
- Во-первых, определите новую функцию, которая загружает данные. Она использует
setTimeout()
для эмуляции асинхронной операции:
const DATA_LOAD_TIME = 5000; function getData() { return new Promise((resolve, reject) => { setTimeout(() => { const message = 'Promise.race() Demo'; resolve(message); }, DATA_LOAD_TIME); }); }
- Во-вторых, разработайте функцию, которая показывает некоторое содержимое:
function showContent(message) { document.querySelector('#message').textContent = message; }
Эту функцию также можно использовать для установки пустого message
.
- В-третьих, определите, что функция
timeout()
возвращает обещание, которое отклоняется при передачеTIMEOUT
.
const TIMEOUT = 500; function timeout() { return new Promise((resolve, reject) => { setTimeout(() => reject(), TIMEOUT); }); }
- В-четвертых, разработайте пару функций, которые показывают и скрывают индикатор загрузки:
function showLoadingIndicator() { document.querySelector('#loader').className = 'loader'; } function hideLoadingIndicator() { document.querySelector('#loader').className = ''; }
- В-пятых, прикрепите прослушиватель событий щелчка к кнопке « Получить сообщение ». Внутри обработчика кликов используйте статический метод
Promise.race()
:
// handle button click event const btn = document.querySelector('#btnGet'); btn.addEventListener('click', () => { // reset UI if users click the 2nd, 3rd, ... time reset(); // show content or loading indicator Promise.race([getData() .then(showContent) .then(hideLoadingIndicator), timeout() ]) .catch(showLoadingIndicator); });
Мы передаем два промиса в метод Promise.race()
:
Promise.race([getData() .then(showContent) .then(hideLoadingIndicator), timeout() ]) .catch(showLoadingIndicator);
Первый промис получает данные с сервера, показывает содержимое и скрывает индикатор загрузки. Второе обещание устанавливает тайм-аут.
Если для выполнения первого промиса требуется более 500 мс, вызывается catch()
для отображения индикатора загрузки. Как только первое обещание разрешается, оно скрывает индикатор загрузки.
- Наконец, разработайте функцию
reset()
, которая скрывает сообщение и индикатор загрузки, если кнопка нажата во второй раз.
// reset UI function reset() { hideLoadingIndicator(); showContent(''); }
Объединим все это.
// after 0.5 seconds, if the getData() has not resolved, then show // the Loading indicator const TIMEOUT = 500; const DATA_LOAD_TIME = 5000; function getData() { return new Promise((resolve, reject) => { setTimeout(() => { const message = 'Promise.race() Demo'; resolve(message); }, DATA_LOAD_TIME); }); } function showContent(message) { document.querySelector('#message').textContent = message; } function timeout() { return new Promise((resolve, reject) => { setTimeout(() => reject(), TIMEOUT); }); } function showLoadingIndicator() { document.querySelector('#loader').className = 'loader'; } function hideLoadingIndicator() { document.querySelector('#loader').className = ''; } // handle button click event const btn = document.querySelector('#btnGet'); btn.addEventListener('click', () => { // reset UI if users click the second time reset(); // show content or loading indicator Promise.race([getData() .then(showContent) .then(hideLoadingIndicator), timeout() ]) .catch(showLoadingIndicator); }); // reset UI function reset() { hideLoadingIndicator(); showContent(''); }