文章

Promise静态方法原理深度解析

Promise静态方法原理深度解析

一句话概括

Promise.allPromise.racePromise.allSettledPromise.any 是 Promise 的四个核心静态方法,分别对应”全部成功”、”最快完成”、”全部落定”、”任一成功”四种并发控制语义,掌握其原理是异步编程进阶的必经之路。


背景

在实际业务中,我们经常需要同时发起多个异步请求,并根据这些请求的结果做出不同的处理:

  • 页面初始化时同时请求用户信息、权限列表、配置数据,全部成功才渲染
  • 多个 CDN 节点同时请求资源,哪个快用哪个
  • 批量操作后无论成功失败都要汇总结果
  • 多个备用接口中任意一个成功即可

这四种场景分别对应 Promise.allPromise.racePromise.allSettledPromise.any


概念与定义

Promise.all

1
Promise.all(iterable)
  • 接收一个可迭代对象(通常是数组)
  • 全部 resolve → 返回所有结果的数组(顺序与输入一致)
  • 任意一个 reject → 立即 reject,返回第一个失败的原因
  • 空数组 → 立即 resolve []

Promise.race

1
Promise.race(iterable)
  • 接收一个可迭代对象
  • 第一个落定(无论 resolve 还是 reject) → 以该结果落定
  • 空数组 → 永远 pending(永不落定)

Promise.allSettled

1
Promise.allSettled(iterable)
  • ES2020 引入
  • 等待所有 Promise 落定(无论成功失败)
  • 返回结果数组,每项格式:
    • { status: 'fulfilled', value: ... }
    • { status: 'rejected', reason: ... }
  • 永远不会 reject

Promise.any

1
Promise.any(iterable)
  • ES2021 引入
  • 任意一个 resolve → 立即 resolve,返回第一个成功的值
  • 全部 reject → reject,抛出 AggregateError(包含所有失败原因)
  • 空数组 → 立即 reject AggregateError

最小示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
const p1 = Promise.resolve(1);
const p2 = Promise.resolve(2);
const p3 = Promise.reject('error');

// Promise.all:全部成功
Promise.all([p1, p2]).then(console.log); // [1, 2]

// Promise.all:任一失败
Promise.all([p1, p3]).catch(console.log); // 'error'

// Promise.race:最快落定
Promise.race([
  new Promise(r => setTimeout(() => r('slow'), 1000)),
  new Promise(r => setTimeout(() => r('fast'), 100)),
]).then(console.log); // 'fast'

// Promise.allSettled:全部落定
Promise.allSettled([p1, p3]).then(console.log);
// [
//   { status: 'fulfilled', value: 1 },
//   { status: 'rejected', reason: 'error' }
// ]

// Promise.any:任一成功
Promise.any([p3, p1]).then(console.log); // 1

核心知识点拆解

1. Promise.all 的短路特性

Promise.all 一旦有一个 Promise reject,会立即 reject,不会等待其他 Promise 完成。但其他 Promise 仍然在执行,只是结果被忽略了。

1
2
3
4
5
6
const p1 = new Promise((_, reject) => setTimeout(() => reject('p1 fail'), 100));
const p2 = new Promise(resolve => setTimeout(() => resolve('p2 ok'), 200));

Promise.all([p1, p2])
  .catch(err => console.log('caught:', err)); // caught: p1 fail
// p2 仍然会在 200ms 后 resolve,但结果被忽略

2. Promise.all 结果顺序保证

结果数组的顺序与输入数组的顺序严格一致,与 Promise 的完成顺序无关:

1
2
3
4
const p1 = new Promise(r => setTimeout(() => r('slow'), 200));
const p2 = new Promise(r => setTimeout(() => r('fast'), 50));

Promise.all([p1, p2]).then(console.log); // ['slow', 'fast'](顺序与输入一致)

3. Promise.race 与 Promise.any 的区别

方法触发条件失败处理
race第一个落定(成功或失败)第一个 reject 就 reject
any第一个成功全部失败才 reject
1
2
3
4
5
const fail = Promise.reject('fail');
const slow = new Promise(r => setTimeout(() => r('ok'), 100));

Promise.race([fail, slow]).catch(e => console.log('race:', e)); // race: fail
Promise.any([fail, slow]).then(v => console.log('any:', v));   // any: ok

4. Promise.allSettled 的使用场景

适合批量操作后汇总结果,不关心单个失败:

1
2
3
4
5
6
7
8
const requests = [fetchUser(1), fetchUser(2), fetchUser(3)];

Promise.allSettled(requests).then(results => {
  const succeeded = results.filter(r => r.status === 'fulfilled').map(r => r.value);
  const failed = results.filter(r => r.status === 'rejected').map(r => r.reason);
  console.log('成功:', succeeded);
  console.log('失败:', failed);
});

5. 非 Promise 值的处理

所有静态方法都会将非 Promise 值通过 Promise.resolve() 包装:

1
Promise.all([1, 2, Promise.resolve(3)]).then(console.log); // [1, 2, 3]

实战案例

案例一:页面初始化并发请求

1
2
3
4
5
6
7
8
9
10
11
12
async function initPage() {
  try {
    const [userInfo, permissions, config] = await Promise.all([
      fetchUserInfo(),
      fetchPermissions(),
      fetchConfig(),
    ]);
    renderPage(userInfo, permissions, config);
  } catch (err) {
    showError('页面初始化失败:' + err.message);
  }
}

案例二:CDN 容灾切换

1
2
3
4
5
6
7
8
9
10
async function fetchWithFallback(urls) {
  try {
    // 任意一个成功即可
    const result = await Promise.any(urls.map(url => fetch(url)));
    return result;
  } catch (e) {
    // AggregateError:所有 CDN 都失败
    throw new Error('所有 CDN 节点均不可用');
  }
}

案例三:批量删除汇总结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
async function batchDelete(ids) {
  const results = await Promise.allSettled(ids.map(id => deleteItem(id)));
  
  const successIds = [];
  const failedIds = [];
  
  results.forEach((result, index) => {
    if (result.status === 'fulfilled') {
      successIds.push(ids[index]);
    } else {
      failedIds.push({ id: ids[index], reason: result.reason });
    }
  });
  
  return { successIds, failedIds };
}

案例四:超时控制

1
2
3
4
5
6
7
8
9
10
11
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error(`超时:${ms}ms`)), ms)
  );
  return Promise.race([promise, timeout]);
}

// 使用
withTimeout(fetchData(), 3000)
  .then(data => console.log(data))
  .catch(err => console.log(err.message)); // 超时:3000ms

底层原理

Promise.all 实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Promise.myAll = function(iterable) {
  return new Promise((resolve, reject) => {
    const promises = Array.from(iterable);
    
    if (promises.length === 0) {
      resolve([]);
      return;
    }
    
    const results = new Array(promises.length);
    let resolvedCount = 0;
    
    promises.forEach((p, index) => {
      // 用 Promise.resolve 包装,处理非 Promise 值
      Promise.resolve(p).then(
        value => {
          results[index] = value; // 保证顺序
          resolvedCount++;
          if (resolvedCount === promises.length) {
            resolve(results); // 全部完成才 resolve
          }
        },
        reason => {
          reject(reason); // 任一失败立即 reject
        }
      );
    });
  });
};

Promise.race 实现原理

1
2
3
4
5
6
7
8
9
Promise.myRace = function(iterable) {
  return new Promise((resolve, reject) => {
    for (const p of iterable) {
      // 第一个落定的 Promise 决定最终结果
      // 后续的 resolve/reject 调用会被忽略(Promise 状态不可逆)
      Promise.resolve(p).then(resolve, reject);
    }
  });
};

Promise.allSettled 实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
Promise.myAllSettled = function(iterable) {
  return new Promise(resolve => {
    const promises = Array.from(iterable);
    
    if (promises.length === 0) {
      resolve([]);
      return;
    }
    
    const results = new Array(promises.length);
    let settledCount = 0;
    
    promises.forEach((p, index) => {
      Promise.resolve(p).then(
        value => {
          results[index] = { status: 'fulfilled', value };
          settledCount++;
          if (settledCount === promises.length) resolve(results);
        },
        reason => {
          results[index] = { status: 'rejected', reason };
          settledCount++;
          if (settledCount === promises.length) resolve(results);
        }
      );
    });
  });
};

Promise.any 实现原理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Promise.myAny = function(iterable) {
  return new Promise((resolve, reject) => {
    const promises = Array.from(iterable);
    
    if (promises.length === 0) {
      reject(new AggregateError([], 'All promises were rejected'));
      return;
    }
    
    const errors = new Array(promises.length);
    let rejectedCount = 0;
    
    promises.forEach((p, index) => {
      Promise.resolve(p).then(
        value => {
          resolve(value); // 任一成功立即 resolve
        },
        reason => {
          errors[index] = reason;
          rejectedCount++;
          if (rejectedCount === promises.length) {
            // 全部失败才 reject
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        }
      );
    });
  });
};

高频面试题解析

Q1:Promise.all 和 Promise.allSettled 的区别?

答:

对比项Promise.allPromise.allSettled
引入版本ES2015ES2020
失败处理任一失败立即 reject等待全部落定,不会 reject
返回值成功值数组{status, value/reason} 数组
适用场景全部成功才有意义需要汇总所有结果

Q2:Promise.race 和 Promise.any 的区别?

答:

  • Promise.race:第一个落定(无论成功失败)就决定结果。如果第一个是 reject,整体就 reject。
  • Promise.any:第一个成功才决定结果。只有全部失败才 reject,并抛出 AggregateError

Q3:Promise.all 传入空数组会怎样?

答: 立即 resolve 一个空数组 []

1
Promise.all([]).then(v => console.log(v)); // []

Promise.race([]) 会永远 pending,因为没有任何 Promise 可以落定。

Q4:Promise.all 中某个 Promise reject 后,其他 Promise 还会执行吗?

答: 会继续执行,但结果会被忽略。Promise.all 只是不再等待其他结果,但无法取消已经发出的异步操作(如网络请求)。如果需要取消,需要配合 AbortController

Q5:如何实现一个带超时的 Promise.all?

1
2
3
4
5
6
function allWithTimeout(promises, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('Timeout')), ms)
  );
  return Promise.race([Promise.all(promises), timeout]);
}

Q6:AggregateError 是什么?

答: ES2021 引入的错误类型,专门用于表示多个错误的集合。Promise.any 在全部 reject 时抛出,包含 errors 属性(所有失败原因的数组)。

1
2
3
4
5
Promise.any([Promise.reject('a'), Promise.reject('b')])
  .catch(e => {
    console.log(e instanceof AggregateError); // true
    console.log(e.errors); // ['a', 'b']
  });

总结与扩展

四个方法对比总结

方法成功条件失败条件返回值引入版本
all全部 resolve任一 reject结果数组ES2015
race第一个落定第一个落定第一个结果ES2015
allSettled全部落定永不失败状态对象数组ES2020
any任一 resolve全部 reject第一个成功值ES2021

扩展阅读

  1. Promise.all 的并发控制Promise.all 会同时发起所有请求,如果需要限制并发数,需要手动实现调度器(下周主题)
  2. AbortController 配合使用:在 Promise.race 超时后,通过 AbortController 取消未完成的请求
  3. Promise.any 的 Polyfill:在不支持 ES2021 的环境中,可以用 Promise.all + 反转逻辑实现

记忆口诀

  • all:全赢才赢,一输即输
  • race:第一名说了算
  • allSettled:等所有人跑完,不管名次
  • any:有一个赢就赢,全输才输
本文由作者按照 CC BY 4.0 进行授权