【js篇】Promise.all vs Promise.race:深度对比与实战场景

29 阅读4分钟

在 JavaScript 异步编程中,Promise.allPromise.race 是两个强大的组合工具,它们都能处理多个异步任务,但设计理念和使用场景截然不同。

本文将深入剖析两者的核心区别,并通过真实业务场景,告诉你“什么时候用哪个”。


一、核心概念对比

特性Promise.allPromise.race
中文含义“全部”“竞速”
成功条件所有 Promise 都 fulfilled第一个 fulfilled 的 Promise
失败条件第一个 rejected 的 Promise第一个 rejected 的 Promise
返回值数组:按传入顺序排列的结果单个值:最快完成的 Promise 的结果或错误
执行顺序并发执行,不保证完成顺序并发执行,谁快谁赢
典型用途并行请求、数据聚合超时控制、竞速加载

二、Promise.all:全都要,一个都不能少

✅ 核心逻辑

“等大家都到齐了,我们再开会。”

  • 所有传入的 Promise 并发执行
  • 只有当所有都成功时,Promise.all 才 resolve;
  • 如果任一个失败,立即 reject,返回第一个失败的原因
  • 成功时返回数组顺序与传入数组一致

✅ 语法

Promise.all([promise1, promise2, promise3])
  .then(results => {
    // results 是数组,顺序固定
    console.log(results); // [result1, result2, result3]
  })
  .catch(error => {
    // 任一失败,立即捕获
    console.error('失败原因:', error);
  });

✅ 实战场景 1:并行加载多个资源

// 同时加载用户信息、文章列表、评论数据
Promise.all([
  fetch('/api/user').then(res => res.json()),
  fetch('/api/posts').then(res => res.json()),
  fetch('/api/comments').then(res => res.json())
])
.then(([user, posts, comments]) => {
  console.log('用户:', user);
  console.log('文章:', posts);
  console.log('评论:', comments);
  // 渲染完整页面
})
.catch(err => {
  console.error('任一请求失败,页面加载中断:', err);
});

优势:比串行快,且能保证所有数据就绪后再渲染。


✅ 实战场景 2:表单验证(所有字段必须通过)

const validateEmail = () => api.checkEmail('user@126.com');
const validatePhone = () => api.checkPhone('13800138000');
const validateUsername = () => api.checkUsername('john_doe');

Promise.all([
  validateEmail(),
  validatePhone(),
  validateUsername()
])
.then(() => {
  console.log('所有验证通过,可以提交!');
})
.catch(err => {
  console.error('验证失败:', err.message); // 第一个失败的验证
});

注意:只要一个字段验证失败,整个提交就失败。


三、Promise.race:谁快用谁,落后淘汰

✅ 核心逻辑

“谁先冲过终点线,谁就是冠军。”

  • 所有传入的 Promise 并发执行
  • 返回第一个完成的 Promise 的结果(无论成功或失败);
  • 一旦有结果,其他 Promise 的结果被忽略

✅ 语法

Promise.race([promise1, promise2, promise3])
  .then(firstResult => {
    console.log('最快的结果:', firstResult);
  })
  .catch(firstError => {
    console.error('最快的错误:', firstError);
  });

✅ 实战场景 1:设置请求超时

这是 Promise.race 最经典的用法!

// 创建一个超时 Promise
function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => {
      reject(new Error(`请求超时 (${ms}ms)`));
    }, ms);
  });
}

// 发起请求,并设置 5 秒超时
Promise.race([
  fetch('/api/slow-data'), // 真实请求
  timeout(5000)            // 超时控制
])
.then(response => response.json())
.then(data => {
  console.log('数据加载成功:', data);
})
.catch(err => {
  console.error('请求失败:', err.message); // 可能是网络错误,也可能是超时
});

优势:避免用户无限等待,提升用户体验。


✅ 实战场景 2:竞速加载(CDN 切换)

从多个 CDN 加载同一资源,谁快用谁。

const cdnUrls = [
  'https://cdn1.example.com/data.json',
  'https://cdn2.example.com/data.json',
  'https://cdn3.example.com/data.json'
];

const requests = cdnUrls.map(url => 
  fetch(url).then(res => res.json())
);

Promise.race(requests)
  .then(data => {
    console.log('最快 CDN 返回数据:', data);
  })
  .catch(err => {
    console.error('所有 CDN 都失败:', err);
  });

优势:提升资源加载速度,增强系统健壮性。


✅ 实战场景 3:游戏中的“抢答”逻辑

// 模拟两个玩家抢答
const player1 = new Promise(resolve => {
  setTimeout(() => resolve('玩家A抢答成功!'), 2000);
});

const player2 = new Promise(resolve => {
  setTimeout(() => resolve('玩家B抢答成功!'), 1500);
});

Promise.race([player1, player2])
  .then(winner => {
    console.log(winner); // "玩家B抢答成功!"
  });

四、关键差异详解

1️⃣ 成功返回值不同

const p1 = Promise.resolve('A');
const p2 = Promise.resolve('B');

// Promise.all 返回数组
Promise.all([p1, p2]).then(console.log); // ['A', 'B']

// Promise.race 返回单个值
Promise.race([p1, p2]).then(console.log); // 'A' (谁快谁先,这里几乎同时)

2️⃣ 失败行为不同

const success = Promise.resolve('Success');
const failFast = Promise.reject('Error 1');
const failSlow = new Promise((_, reject) => 
  setTimeout(() => reject('Error 2'), 1000)
);

// Promise.all:第一个 reject 就失败
Promise.all([success, failFast, failSlow])
  .catch(console.log); // 输出: Error 1

// Promise.race:第一个完成的 Promise 决定结果
Promise.race([success, failFast, failSlow])
  .catch(console.log); // 输出: Error 1 (failFast 最快)

3️⃣ 顺序保证不同

const slow = new Promise(r => setTimeout(() => r('慢'), 2000));
const fast = new Promise(r => setTimeout(() => r('快'), 1000));

Promise.all([slow, fast]).then(console.log);
// 输出: ['慢', '快'] → 顺序按传入数组,不是完成顺序

Promise.race([slow, fast]).then(console.log);
// 输出: '快' → 谁快谁赢

五、如何选择?决策树

graph TD
    A[需要处理多个异步任务?] --> B{是否必须全部成功?}
    B -->|是| C[使用 Promise.all]
    B -->|否| D{是否只关心最快的结果?}
    D -->|是| E[使用 Promise.race]
    D -->|否| F[考虑 Promise.allSettled 或 Promise.any]

六、注意事项

⚠️ Promise.all 的“短路”特性

一旦有一个 Promise rejectPromise.all 立即失败,后续 Promise 仍会执行,但结果被忽略。

const p1 = Promise.reject('Fail');
const p2 = new Promise(r => {
  console.log('p2 仍在执行...'); // 会打印
  setTimeout(() => r('Done'), 1000);
});

Promise.all([p1, p2]).catch(() => {}); // p2 依然执行

🔧 如果你想等所有结果(无论成败),用 Promise.allSettled


💡 结语

Promise.all 是团队协作,Promise.race 是个人竞速。”

用法记忆口诀
Promise.all“全勤奖” —— 大家都到才发奖
Promise.race“冠军赛” —— 第一名说了算

掌握它们的区别,你就能:

  • all 实现高效并行;
  • race 实现超时控制和竞速加载。