在本教程中,您将学习如何使用 JavaScript Promise.race() 静态方法。

JavaScript Promise.race() 静态方法介绍

Promise.race() 静态方法接受一个 Promise 一个可迭代的列表对象,并返回一个新的 Promise,一旦有一个 Promise 解决或拒绝,就会返回一个解决或拒绝的 Promise,以及 Promise 的值或拒绝原因。

下面是 Promise.race() 方法的语法:

Promise.race(iterable)

在此语法中,iterable 是一个包含 Promise 列表的可迭代对象

Promise.race() 的名称意味着所有的 Promise 都相互竞争,无论是解决还是拒绝。

见下图:

在此图中:

  • promise1t1 时返回 v1 值并解决。
  • promise2error 在 t2 被拒绝。
  • 因为 promise1promise2 更早解决,所以 promise1 赢得竞争。因此,Promise.race([promise1, promise2]) 返回一个新的 Promise, 在 t1 时被解决与 v1 值。

再看一张图:

在此图中:

  • promise1t2 时被解决与 v1 值。
  • promise2errort1 时被拒绝。
  • 因为 promise2promise1 更早解决,所以 promise2 赢得竞争。因此,Promise.race([promise1, promise2]) 返回一个在 t1 时因 error 被拒绝Promise 。

JavaScript Promise.race() 示例

让我们举一些使用 Promise.race() 静态方法的例子。

简单的 JavaScript Promise.race()

下面创建两个 Promise:一个在 1 秒内解决,另一个在 2 秒内解决。因为第一个 Promise 比第二个解决得更快,所以 Promise.race() 使用第一个 Promise 的值解决:

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

下面示例创建两个承诺。第一个 promise 在 1 秒内解决,而第二个 promise 在 2 秒内拒绝。因为第一个 promise 比第二个快,所以返回的 promise 解析为第一个 promise 的值:

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
请注意,如果第二个 promise 比第一个快,则返回的 promise 会因为第二个 promise 的原因而被拒绝。

JavaScript Promise.race() 实践

假设如果从服务器加载数据的时间超过几秒,您必须显示一个加载指示器。

因此,您可以使用 Promise.race() 静态方法。如果发生超时,则显示加载指示器,否则显示消息。

下面一段简单的 HTML 代码:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>JavaScript Promise.race() Demo</title>
    <style>
        body {
    font-family: 'Open Sans', sans-serif;
    background-color: #fff;
    display: flex;
    align-items: center;
    justify-content: center;
    min-height: 100vh;
    margin: 0;
}

#container {
    background-color: #fff;
    border-radius: 5px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
    max-width: 400px;
    margin: 10px auto;
    padding: 16px;
    width: 300px;
    text-align: center;
}

#message {
    margin-bottom: 10px;
    padding: 10px 5px 10px;
    text-align: left;
}

button {
    box-sizing: border-box;
    width: 100%;
    padding: 3%;
    background: #007bff;
    border-bottom: 2px solid #007bff;
    border-top-style: none;
    border-right-style: none;
    border-left-style: none;
    color: #fff;
}

button:hover {
    background: #0069d9;
    cursor: pointer;
}


.loader {
    border: 8px solid #f3f3f3;
    border-radius: 50%;
    border-top: 8px solid #F9DC5C;
    width: 25px;
    height: 25px;
    margin: 0 auto;
    text-align: center;
    -webkit-animation: spin 2s linear infinite;
    /* Safari */
    animation: spin 2s linear infinite;
}

/* Safari */
@-webkit-keyframes spin {
    0% {
        -webkit-transform: rotate(0deg);
    }

    100% {
        -webkit-transform: rotate(360deg);
    }
}

@keyframes spin {
    0% {
        transform: rotate(0deg);
    }

    100% {
        transform: rotate(360deg);
    }
}
    </style>
</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 动画。从技术上讲,如果一个元素有类 .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() 函数并返回 Promise。当经过指定的时间时,Promise将拒绝。

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 = '';
}

第五,将点击事件添加到Get Message按钮。在点击事件回调中,使用Promise.race() 静态方法:

// 按钮点击事件
const btn = document.querySelector('#btnGet');

btn.addEventListener('click', () => {
    // 如果用户点击多次则重置 UI
    reset();
    
    // 显示消息内容或者显示加载器
    Promise.race([getData()
            .then(showContent)
            .then(hideLoadingIndicator), timeout()
        ])
        .catch(showLoadingIndicator);
});

我们将两个 Promise 传递给 Promise.race() 方法:

Promise.race([getData()
            .then(showContent)
            .then(hideLoadingIndicator), timeout()
        ])
        .catch(showLoadingIndicator);

第一个 Promise 从服务器获取数据,显示内容,并隐藏加载指示器。第二个 Promise 设置超时。

如果第一个 promise 需要超过 500 毫秒才能解决,则调用 catch() 显示加载指示器。一旦第一个 promise 解决,它就会隐藏加载指示器。

最后,定义 reset() 函数,如果第二次单击按钮,则隐藏消息和加载指示器。

// 重置 UI
function reset() {
    hideLoadingIndicator();
    showContent('');
}

把它们放在一起。

// 如果显示加载指示器 0.5秒后 getData() 没有解决
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 = '';
}


//按钮点击事件
const btn = document.querySelector('#btnGet');

btn.addEventListener('click', () => {
    // 如果用户点击第二次则重置 UI
    reset();

    // 显示消息内容或者加载指示器
    Promise.race([getData()
            .then(showContent)
            .then(hideLoadingIndicator), timeout()
        ])
        .catch(showLoadingIndicator);
});

// 重置UI
function reset() {
    hideLoadingIndicator();
    showContent('');
}

结论

  • Promise.race(iterable) 方法返回一个新的 Promise,一旦 iterable 中的 Promise 之一解决或拒绝,该 Promise 就会解决或拒绝,并返回该 Promise 的值或错误。