Promise:一文看懂 all、race、any 和 allSettled 的使用

354 阅读8分钟

在前端开发中,我们经常需要同时处理多个异步任务,比如请求多个接口、加载多个资源等。为了更高效地管理这些并发操作,JavaScript 提供了 Promise 的几个静态方法:Promise.allPromise.racePromise.anyPromise.allSettled

它们各有特点:有的要求全部成功才算成功,有的只看谁跑得快,有的只要有一个成功就行,还有的会等所有任务结束并返回每个的结果。掌握这些方法,能让我们更灵活、简洁地处理复杂的异步场景。

本文将用简单的方式介绍这几种方法的用途和区别,帮助你快速理解和使用。


1. Promise.all

定义
Promise.all 接收一个 Promise 可迭代对象(如数组),并返回一个新的 Promise。只有当所有输入的 Promise 都成功(fulfilled)时,新 Promise 才会成功,结果是一个按顺序排列的值数组;如果任意一个 Promise 失败(rejected),则立即失败并返回第一个错误。

特点

  • 全成功才成功:所有 Promise 必须完成且无错误。
  • 立即失败:只要有一个 Promise 失败,整个结果失败。
  • 顺序保持:结果数组的顺序与输入顺序一致,即使某些 Promise 执行较慢。

适用场景

  • 并发处理多个独立任务,且需要所有任务都成功后才能继续(如同时获取多个接口数据)。
  • 需要严格依赖所有任务结果的场景(如渲染页面前需加载所有资源)。

基础代码:

// p1: 一个立即成功(fulfilled)的 Promise,值为 'p1 resolve',用于模拟瞬间完成的异步操作。
const p1 = Promise.resolve('p1 resolve')

// p2: 一个延迟 2 秒后成功(fulfilled)的 Promise,值为 'p2 延迟2s',用于模拟耗时约 2 秒的异步请求。
const p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p2 延迟2s')
    }, 2000)
})

// p3: 一个延迟 3 秒后成功(fulfilled)的 Promise,值为 'p3 延迟3s',用于模拟耗时约 3 秒的异步请求。
const p3 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('p3 延迟3s')
    }, 3000)
})

// p4: 一个立即失败(rejected)的 Promise,原因为 'p4 reject',用于模拟同步阶段就出错的场景。
const p4 = Promise.reject('p4 reject')

// p5: 一个延迟 1 秒后失败(rejected)的 Promise,原因为 'p5 延迟 1s',用于模拟异步操作中出现错误(如网络超时)。
const p5 = new Promise((resolve, reject) => {
    setTimeout(() => {
        reject('p5 延迟 1s')
    }, 1000)
})

示例

// 案例1:所有 Promise 成功
Promise.all([p1, p2, p3])
  .then((results) => {console.log(results);}) // ["p1 resolve", "p2 延迟2s", "p3 延迟3s"]
  .catch((error) => {console.error(error);});
// 结果顺序与输入顺序一致,即使 p3 最慢
// 案例2:一个 Promise 立即失败(p4)
Promise.all([p1, p4, p2])
  .then((results) => {console.log(results);}) // 不会触发
  .catch((error) => {console.error(error);  });// "p4 reject"
// 任意一个失败,立即返回第一个错误
// 案例3:一个 Promise 延迟失败(p5)
Promise.all([p1, p5, p2])
 .then((results) => {console.log(results);}) // 不会触发
 .catch((error) => {console.error(error);}); // "p5 延迟 1s"
// p5 是第一个失败的 Promise,all 立即返回错误
 
// 案例4:不同执行时间的 Promise,验证顺序保持
Promise.all([p2, p3, p5])
  .then((results) => {console.log(results); })// ["p2 延迟2s", "p3 延迟3s", "p5 延迟 1s"]
  .catch((error) => {console.error(error);}); 
  // "p5 延迟 1s"(因 p5 失败导致 all 立即失败)
  // 结果顺序与输入顺序一致,即使 p5 最快完成 

效果描述

  1. 案例1:所有 Promise 成功,结果数组的顺序与输入顺序一致(p1p2p3),即使 p3 最慢完成。
    验证了 顺序保持 的特性。
  2. 案例2p4 是第一个失败的 Promise,导致整个 Promise.all 立即失败,返回 p4 的错误信息。
    验证了 立即失败 的特性。
  3. 案例3p5 是第一个失败的 Promiseall 立即返回其错误,无需等待其他 Promise 完成。
    验证了 立即失败 的特性。
  4. 案例4:尽管 p5 最快完成,但结果数组仍与输入顺序一致。

2. Promise.race

定义
Promise.race 接收一个 Promise 可迭代对象,并返回一个新的 Promise。新 Promise 的状态由第一个完成(无论成功或失败)的 Promise 决定。

特点

  • 谁快听谁的:无论成功或失败,第一个完成的 Promise 决定最终结果。
  • 竞速逻辑:适用于超时控制或优先响应的场景。

适用场景

  • 需要快速响应的场景(如网络请求的超时处理)。
  • 竞争式任务(如多个异步操作中选择最快的结果)。 示例
// 案例1:第一个是成功的 Promise(p1)
Promise.race([p1, p2, p3])
  .then((result) => {console.log(result);}) // "p1 resolve"(p1 最快完成)
  .catch((error) => {console.error(error);}); // 不会触发,因为 p1 成功
// 案例2:第一个是失败的 Promise(p4)
Promise.race([p4, p5])
  .then((result) => {console.log(result);}) // 不会触发
  .catch((error) => {console.error(error);}); // "p4 reject"(p4 最快完成且失败) 
// 案例3:混合成功和失败的 Promise(p5 延迟失败)
Promise.race([p5, p2])
  .then((result) => {console.log(result);}) // 不会触发
  .catch((error) => {console.error(error);}); // "p5 延迟 1s"(p5 比 p2 更快完成且失败)

效果描述

  1. 案例1p1 是最快的,且成功,因此结果直接返回 p1 的值。
  2. 案例2p4 是最快的,但失败,因此整个 race 直接返回 p4 的错误。
  3. 案例3p5 比 p2 更快完成,但失败,因此结果由 p5 决定。
    验证了 race 的核心特性:第一个完成的 Promise(无论成功或失败)决定了最终结果。

3. Promise.any

定义
Promise.any 接收一个 Promise 可迭代对象,并返回一个新的 Promise。新 Promise 在任意一个输入的 Promise 成功时立即成功,结果为该成功值;只有当所有 Promise 都失败时才会失败,返回一个包含所有错误的 AggregateError

特点

  • 首个成功即成功:忽略其他失败的 Promise
  • 全失败才失败:所有 Promise 失败时,返回聚合错误。

适用场景

  • 并行尝试多个操作,只需一个成功即可(如多服务器请求,任选一个可用结果)。
  • 容错性要求高的场景(如备用方案选择)。

示例

// 案例1:至少一个成功(p1)
Promise.any([p1, p4, p5])
  .then((result) => {console.log(result);}) // "p1 resolve"(p1 成功)
  .catch((error) => {console.error(error);}); // 不会触发
// 案例2:全部失败(p4、p5)
Promise.any([p4, p5])
  .then((result) => {console.log(result);}) // 不会触发
  .catch((error) => {console.error(error);}); // AggregateError(包含所有错误)
// 案例3:混合成功和失败的 Promise(p1 成功)
Promise.any([p4, p5, p1])
  .then((result) => {console.log(result);}) // "p1 resolve"(p1 成功)
  .catch((error) => {console.error(error);}); // 不会触发

效果描述

  1. 案例1p1 成功,因此直接返回其值,忽略 p4 和 p5 的失败。
  2. 案例2:所有 Promise 都失败,返回一个 AggregateError,包含所有错误信息。
  3. 案例3p1 是唯一成功项,因此直接返回其值。
    验证了 any 的核心特性:只要有一个成功就立即返回,只有全失败时才返回聚合错误。

4. Promise.allSettled

定义
Promise.allSettled 接收一个 Promise 可迭代对象,并返回一个新的 Promise。新 Promise 在所有输入的 Promise 完成(无论成功或失败)后才完成,结果是一个包含每个 Promise 状态和结果的数组。

特点

  • 全部完成才结束:无论成功或失败,等待所有 Promise 结束。
  • 结果结构化:返回的数组包含每个 Promisestatusfulfilledrejected)和 value/reason

适用场景

  • 需要收集所有任务结果(无论成功或失败)的场景(如批量上传文件,记录每个文件的状态)。
  • 对错误容忍度高的场景(如统计多个独立操作的执行情况)。

示例

// 案例1:部分成功,部分失败
Promise.allSettled([p1, p4, p5])
  .then((results) => {console.log(results);});
/* 输出:
      [
        { status: "fulfilled", value: "p1 resolve" },
        { status: "rejected", reason: "p4 reject" },
        { status: "rejected", reason: "p5 延迟 1s" }
      ]
*/  
// 案例2:全部成功
Promise.allSettled([p1, p2, p3])
  .then((results) => {console.log(results);});
/* 输出:
      [
        { status: "fulfilled", value: "p1 resolve" },
        { status: "fulfilled", value: "p2 延迟2s" },
        { status: "fulfilled", value: "p3 延迟3s" }
      ]
*/
// 案例3:全部失败
Promise.allSettled([p4, p5])
  .then((results) => {console.log(results);});
/* 输出:
      [
        { status: "rejected", reason: "p4 reject" },
        { status: "rejected", reason: "p5 延迟 1s" }
      ]
*/

效果描述

  1. 案例1p1 成功,p4 和 p5 失败,返回的数组包含所有状态和结果。
  2. 案例2:所有 Promise 成功,返回的数组中每个对象的 status 为 fulfilled
  3. 案例3:所有 Promise 失败,返回的数组中每个对象的 status 为 rejected
    验证了 allSettled 的核心特性:无论成功或失败,都会等待所有 Promise 完成,并返回结构化的结果。

总结

方法行为特点适用场景
Promise.all全部成功才成功,任一失败立即失败需要所有任务成功后继续
Promise.race第一个完成的决定结果(无论成功或失败)竞速逻辑或超时控制
Promise.any任一成功即成功,全失败返回聚合错误多尝试中取第一个成功
Promise.allSettled等待所有完成,返回每个任务的详细状态需要统计所有任务结果(无论成功或失败)