Promise.race()

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

Promise.race() 静态方法接受一个 promise 可迭代对象作为输入,并返回一个 Promise。这个返回的 promise 会随着第一个 promise 的敲定而敲定。

尝试一下

语法

js
Promise.race(iterable)

参数

iterable

一个 promise 可迭代对象(例如数组)。

返回值

一个 Promise,会以 iterable 中第一个敲定的 promise 的状态异步敲定。换句话说,如果第一个敲定的 promise 被兑现,那么返回的 promise 也会被兑现;如果第一个敲定的 promise 被拒绝,那么返回的 promise 也会被拒绝。如果传入的 iterable 为空,返回的 promise 就会一直保持待定状态。如果传入的 iterable 非空但其中没有任何一个 promise 是待定状态,返回的 promise 仍会异步敲定(而不是同步敲定)。

描述

Promise.race() 方法是 Promise 并发方法之一。当你想要第一个异步任务完成时,但不关心它的最终状态(即它既可以成功也可以失败)时,它就非常有用。

如果可迭代对象中包含一个或多个非 promise 值和/或已敲定的 promise,则 Promise.race() 将以可迭代对象中找到的第一个此类值敲定。

示例

使用 Promise.race()

这个例子展示了如何使用 Promise.race() 来比较多个使用 setTimeout() 实现的计时器。计时时间最短的计时器总是赢得竞态,并成为返回的 promise 状态。

js
function sleep(time, value, state) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (state === "兑现") {
        return resolve(value);
      } else {
        return reject(new Error(value));
      }
    }, time);
  });
}

const p1 = sleep(500, "一", "兑现");
const p2 = sleep(100, "二", "兑现");

Promise.race([p1, p2]).then((value) => {
  console.log(value); // “二”
  // 两个都会兑现,但 p2 更快
});

const p3 = sleep(100, "三", "兑现");
const p4 = sleep(500, "四", "拒绝");

Promise.race([p3, p4]).then(
  (value) => {
    console.log(value); // “三”
    // p3 更快,所以它兑现
  },
  (error) => {
    // 不会被调用
  },
);

const p5 = sleep(500, "五", "兑现");
const p6 = sleep(100, "六", "拒绝");

Promise.race([p5, p6]).then(
  (value) => {
    // 不会被调用
  },
  (error) => {
    console.error(error.message); // “六”
    // p6 更快,所以它拒绝
  },
);

Promise.race 的异步性

以下示例演示了 Promise.race 的异步性。与其他 promise 并发方法不同,Promise.race 总是异步的:即使 iterable 为空,它也永远不会同步地完成。

js
// 传入一个已经解决的 Promise 数组,以尽快触发 Promise.race。
const resolvedPromisesArray = [Promise.resolve(33), Promise.resolve(44)];

const p = Promise.race(resolvedPromisesArray);
// 立即打印 p 的值
console.log(p);

// 使用 setTimeout,我们可以在堆栈为空后执行代码
setTimeout(() => {
  console.log("堆栈现在为空");
  console.log(p);
});

// 按顺序打印:
// Promise { <state>: "pending" }
// 堆栈现在为空
// Promise { <state>: "fulfilled", <value>: 33 }

一个空的可迭代对象会导致返回的 Promise 一直处于待定状态:

js
const foreverPendingPromise = Promise.race([]);
console.log(foreverPendingPromise);
setTimeout(() => {
  console.log("堆栈现在为空");
  console.log(foreverPendingPromise);
});

// 按顺序打印:
// Promise { <state>: "pending" }
// 堆栈现在为空
// Promise { <state>: "pending" }

如果可迭代对象包含一个或多个非 Promise 值和/或一个已经敲定的 Promise,则 Promise.race 会以数组中找到的第一个这样的值敲定:

js
const foreverPendingPromise = Promise.race([]);
const alreadyFulfilledProm = Promise.resolve(100);

const arr = [foreverPendingPromise, alreadyFulfilledProm, "非 Promise 值"];
const arr2 = [foreverPendingPromise, "非 Promise 值", Promise.resolve(100)];
const p = Promise.race(arr);
const p2 = Promise.race(arr2);

console.log(p);
console.log(p2);
setTimeout(() => {
  console.log("堆栈现在为空");
  console.log(p);
  console.log(p2);
});

// 按顺序打印:
// Promise { <state>: "pending" }
// Promise { <state>: "pending" }
// 堆栈现在为空
// Promise { <state>: "fulfilled", <value>: 100 }
// Promise { <state>: "fulfilled", <value>: "非 Promise 值" }

使用 Promise.race() 实现请求超时

你可以使用一个定时器来与一个可能持续很长时间的请求进行竞争,以便超出时间限制时,返回的 Promise 自动拒绝。

js
const data = Promise.race([
  fetch("/api"),
  new Promise((resolve, reject) => {
    // 5 秒后拒绝
    setTimeout(() => reject(new Error("请求超时")), 5000);
  }),
])
  .then((res) => res.json())
  .catch((err) => displayError(err));

如果 data Promise 被兑现,它将包含从 /api 获取的数据;否则,如果 fetch 保持待定状态并输给 setTimeout 定时器,这个 Promise 将在 5 秒后被拒绝。

使用 Promise.race() 检测 Promise 的状态

由于 Promise.race() 解决的可迭代对象中第一个非待定状态的 Promise,我们可以检查一个 Promise 的状态,包括它是否处于待定状态。这个示例是从 promise-status-async 改编而来的。

js
function promiseState(promise) {
  const pendingState = { status: "待定" };

  return Promise.race([promise, pendingState]).then(
    (value) => (value === pendingState ? value : { status: "已兑现", value }),
    (reason) => ({ status: "已拒绝", reason }),
  );
}

在这个函数中,如果 promise 是待定状态,非 Promise 类型的第二个值 pendingState 将成为这个 race 的结果;否则,如果 promise 已经敲定,我们可以通过 onFulfilledonRejected 处理函数来知道它的状态。例如:

js
const p1 = new Promise((res) => setTimeout(() => res(100), 100));
const p2 = new Promise((res) => setTimeout(() => res(200), 200));
const p3 = new Promise((res, rej) => setTimeout(() => rej(300), 100));

async function getStates() {
  console.log(await promiseState(p1));
  console.log(await promiseState(p2));
  console.log(await promiseState(p3));
}

console.log("开始状态:");
getStates();
setTimeout(() => {
  console.log("等待 100ms 后:");
  getStates();
}, 100);

// 打印:
// 开始状态:
// { status: '待定' }
// { status: '待定' }
// { status: '待定' }
// 等待 100ms 后:
// { status: '已兑现', value: 100 }
// { status: '待定' }
// { status: '已拒绝', reason: 300 }

备注: promiseState 函数仍然是异步执行的,因为没有办法同步地获取 Promise 的值(即不使用 then()await),即使它已经敲定。但是,promiseState() 总是在一次事件循环内就会完成,并且实际上从不等待任何 Promise 的敲定。

与 Promise.any() 的比较

Promise.race 方法以可迭代对象中第一个敲定的 Promise 作为敲定值。

js
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, "一");
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(reject, 100, "二");
});

Promise.race([promise1, promise2])
  .then((value) => {
    console.log("成功,值为:", value);
  })
  .catch((reason) => {
    // 只有 promise1 成功兑现,但 promise2 更快
    console.error("失败,原因为:", reason);
  });
// 失败,原因为:二

Promise.any 方法以可迭代对象中第一个被兑现的 Promise 作为兑现值。

js
const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, "一");
});

const promise2 = new Promise((resolve, reject) => {
  setTimeout(reject, 100, "二");
});

Promise.any([promise1, promise2])
  .then((value) => {
    // 只有 promise1 成功兑现,即使 promise2 更快敲定
    console.log("成功,值为:", value);
  })
  .catch((reason) => {
    console.error("失败,原因为:", reason);
  });
// 成功,值为:一

规范

Specification
ECMAScript Language Specification
# sec-promise.race

浏览器兼容性

BCD tables only load in the browser

参见