JavaScript Promise 静态方法

深入解析 Promise 的组合方法:Promise.all 的并发模型、Promise.race 的竞态处理、Promise.allSettled 的全量结果收集、以及 Promise.any 的容错设计。

为什么 Promise 组合方法很重要

单个 Promise 处理单个异步操作,但实际开发中经常需要组合多个异步操作:等待所有操作完成、等待第一个完成、处理部分失败等。

Promise.allPromise.racePromise.allSettledPromise.any 这四个静态方法提供了不同的组合语义,理解它们是掌握 JavaScript 异步编程的关键。


一、Promise.all

1.1 基本用法

Promise.all 等待所有 Promise 完成,任何一个失败则整体失败

const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.resolve(3);

Promise.all([p1, p2, p3])
  .then(values => console.log(values)); // [1, 2, 3]

// 任何一个失败
const pFail = Promise.reject(new Error('failed'));
Promise.all([p1, pFail, p3])
  .catch(err => console.error(err.message)); // 'failed'

1.2 并发模型

Promise.all 并发执行所有 Promise,不会等待一个完成才启动下一个:

const start = Date.now();

Promise.all([
  fetch('/api/1').then(() => console.log(1, Date.now() - start)),
  fetch('/api/2').then(() => console.log(2, Date.now() - start)),
  fetch('/api/3').then(() => console.log(3, Date.now() - start))
]);
// 三个请求几乎同时发出

1.3 空数组

Promise.all([]).then(values => console.log(values)); // []
// 空数组会立即 resolved

二、Promise.race

2.1 基本用法

Promise.race 返回最先完成(无论成功或失败)的 Promise 结果:

const p1 = new Promise(resolve => setTimeout(() => resolve(1), 100));
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 50));
const p3 = new Promise((_, reject) => setTimeout(() => reject(new Error('fail')), 30));

Promise.race([p1, p2, p3])
  .then(value => console.log(value)) // 不执行
  .catch(err => console.error(err.message)); // 'fail'
// p3 最先完成(失败)

2.2 应用场景

超时控制

function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) => 
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([promise, timeout]);
}

withTimeout(fetch('/api/slow'), 5000)
  .then(data => console.log(data))
  .catch(err => console.error(err.message)); // 5秒后 Timeout

三、Promise.allSettled

3.1 基本用法

ES2020 引入。Promise.allSettled 等待所有 Promise 结束,不关心成功或失败

const p1 = Promise.resolve(1);
const p2 = Promise.reject(new Error('failed'));
const p3 = Promise.resolve(3);

Promise.allSettled([p1, p2, p3])
  .then(results => {
    results.forEach((result, i) => {
      if (result.status === 'fulfilled') {
        console.log(`${i}: ${result.value}`);
      } else {
        console.log(`${i}: ${result.reason.message}`);
      }
    });
  });
// 0: 1
// 1: failed
// 2: 3

3.2 应用场景

批量操作的全量报告

// 批量发送通知,不因单个失败而中断
const notifications = [
  { userId: 1, message: 'Hello' },
  { userId: 2, message: 'Hi' },
  { userId: 3, message: 'Hey' }
];

const results = await Promise.allSettled(
  notifications.map(n => sendNotification(n))
);

// 统计成功和失败
const successes = results.filter(r => r.status === 'fulfilled').length;
const failures = results.filter(r => r.status === 'rejected').length;
console.log(`Sent ${successes}, failed ${failures}`);

四、Promise.any

4.1 基本用法

ES2021 引入。Promise.any 返回第一个成功的 Promise,忽略失败

const p1 = Promise.reject(new Error('failed 1'));
const p2 = Promise.resolve(2);
const p3 = Promise.reject(new Error('failed 3'));

Promise.any([p1, p2, p3])
  .then(value => console.log(value)); // 2

4.2 AggregateError

所有 Promise 都失败时,返回 AggregateError

const p1 = Promise.reject(new Error('error 1'));
const p2 = Promise.reject(new Error('error 2'));

Promise.any([p1, p2])
  .catch(err => {
    console.log(err instanceof AggregateError); // true
    console.log(err.errors); // [Error: error 1, Error: error 2]
  });

4.3 应用场景

竞速获取资源

// 从多个 CDN 获取资源,使用最先响应的
const cdn1 = fetch('https://cdn1.example.com/lib.js');
const cdn2 = fetch('https://cdn2.example.com/lib.js');
const cdn3 = fetch('https://cdn3.example.com/lib.js');

const response = await Promise.any([cdn1, cdn2, cdn3]);

五、Promise 组合模式

5.1 并发限制

控制并发数量的模式:

async function parallelLimit(tasks, limit) {
  const results = [];
  const executing = new Set();

  for (const task of tasks) {
    const promise = Promise.resolve().then(() => task());
    results.push(promise);

    if (executing.size >= limit) {
      const done = Promise.race(executing);
      executing.add(done);
      done.then(() => executing.delete(done));
    }
  }

  return Promise.all(results);
}

5.2 顺序执行

// reduce 实现顺序执行
async function sequential(tasks) {
  return tasks.reduce(async (acc, task) => {
    const results = await acc;
    const result = await task();
    return [...results, result];
  }, Promise.resolve([]));
}

5.3 重试机制

async function withRetry(fn, maxAttempts = 3, delay = 1000) {
  for (let i = 0; i < maxAttempts; i++) {
    try {
      return await fn();
    } catch (err) {
      if (i === maxAttempts - 1) throw err;
      await new Promise(r => setTimeout(r, delay));
    }
  }
}

六、面试高频考点

考点 1:Promise.all 的失败模型

Promise.all 采取" fail-fast"模型:任何一个 Promise reject,整体立即 reject。

考点 2:Promise.all vs Promise.allSettled

Promise.all 在任何一个失败时整体失败;Promise.allSettled 等待所有结束,收集每个的详细结果。

考点 3:Promise.race vs Promise.any

Promise.race 返回最先完成的结果(成功或失败);Promise.any 返回最先成功的结果,全部失败时抛出 AggregateError。


延展阅读