文章

Promise静态方法原理深度解析

all → 全赢才赢,race → 第一个落定说了算,allSettled → 不等输赢全汇总,any → 有一个赢就赢。四句话搞定四个静态方法。

Promise静态方法原理深度解析

一句话概括

Promise.allPromise.racePromise.allSettledPromise.any 是四种并发语义的标准化表达——all 求全、race 求快、allSettled 求稳、any 求活


核心知识点

1. 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
Promise.myAll = function (iterable) {
  const promises = Array.from(iterable);
  return new Promise((resolve, reject) => {
    if (promises.length === 0) return resolve([]);

    const results = new Array(promises.length);
    let count = 0;

    promises.forEach((p, i) => {
      Promise.resolve(p).then(
        v => {
          results[i] = v; // 👈 保持输入顺序,不是完成顺序
          if (++count === promises.length) resolve(results);
        },
        e => reject(e)    // 👈 一个失败立即全盘否决
      );
    });
  });
};

// 测试
const slow = new Promise(r => setTimeout(() => r('slow'), 200));
const fast = new Promise(r => setTimeout(() => r('fast'), 50));
Promise.myAll([slow, fast]).then(console.log); // ['slow', 'fast']——顺序和输入一致!

三个细节:空数组直接 resolve []、结果保序、任一失败短路。

2. Promise.race — 第一个落定决定结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Promise.myRace = function (iterable) {
  return new Promise((resolve, reject) => {
    for (const p of iterable) {
      Promise.resolve(p).then(resolve, reject);
      // 后续的 resolve/reject 调用会被 Promise 的状态锁忽略
    }
  });
};

// 超时控制 —— race 的经典用法
function withTimeout(promise, ms) {
  const timeout = new Promise((_, reject) =>
    setTimeout(() => reject(new Error('超时')), ms)
  );
  return Promise.race([promise, timeout]);
}

race 不区分成功失败——谁先到就用谁。空数组传进去会永远 pending

3. Promise.allSettled — 等所有人跑完

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Promise.myAllSettled = function (iterable) {
  const promises = Array.from(iterable);
  return new Promise(resolve => {
    if (promises.length === 0) return resolve([]);

    const results = new Array(promises.length);
    let count = 0;

    promises.forEach((p, i) => {
      Promise.resolve(p).then(
        v  => { results[i] = { status: 'fulfilled', value: v }; if (++count === promises.length) resolve(results); },
        e  => { results[i] = { status: 'rejected', reason: e }; if (++count === promises.length) resolve(results); }
      );
    });
  });
};

永远不会 reject。适合批量操作后汇总结果——成功的有哪些、失败的原因是什么。

4. 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
Promise.myAny = function (iterable) {
  const promises = Array.from(iterable);
  return new Promise((resolve, reject) => {
    if (promises.length === 0) {
      return reject(new AggregateError([], 'All promises were rejected'));
    }

    const errors = new Array(promises.length);
    let count = 0;

    promises.forEach((p, i) => {
      Promise.resolve(p).then(
        v => resolve(v),  // 👈 任一成功直接赢
        e => {
          errors[i] = e;
          if (++count === promises.length) {
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        }
      );
    });
  });
};

anyrace 的关键区别:race 遇到第一个 reject 就认输,any 会继续等,直到有成功的或全部失败。

5. 一张表记住四个方法

方法成功条件失败条件返回值ES 版本
all全部 resolve任一 reject结果数组ES2015
race第一个落定第一个落定第一个结果ES2015
allSettled全部落定永不失败{status, value/reason}[]ES2020
any任一 resolve全部 reject第一个成功值ES2021

「其实你每天都在用」

1. 页面初始化并发加载

1
2
3
4
5
6
const [user, config, permissions] = await Promise.all([
  fetch('/api/user').then(r => r.json()),
  fetch('/api/config').then(r => r.json()),
  fetch('/api/permissions').then(r => r.json()),
]);
// 三个请求同时飞出去,等最慢的回来

2. CDN 多节点容灾

1
2
const cdnUrls = ['https://cdn1.com/file.js', 'https://cdn2.com/file.js', 'https://cdn3.com/file.js'];
const content = await Promise.any(cdnUrls.map(url => fetch(url).then(r => r.text())));

3. 请求超时

1
2
3
4
const data = await Promise.race([
  fetch('/api/slow').then(r => r.json()),
  new Promise((_, r) => setTimeout(() => r(new Error('超时')), 5000)),
]);

4. 批量操作结果汇总

1
2
3
const results = await Promise.allSettled(ids.map(id => deleteItem(id)));
const failed = results.filter(r => r.status === 'rejected');
console.log(`成功 ${results.length - failed.length} 个,失败 ${failed.length} 个`);

5. 多个搜索源竞速

1
2
3
4
5
// 同时搜本地缓存和远程 API,哪个快用哪个
const result = await Promise.any([
  searchCache(query),
  searchRemote(query),
]);

常见误解(FAQ)

❌ 误区 1:「Promise.all 里一个失败其他请求会被取消」

不会。Promise.all 只是不再等待,但已发出的网络请求无法撤销。真正需要取消,要配合 AbortController

❌ 误区 2:「Promise.race 空数组和 Promise.all 空数组行为一样」

不一样。Promise.all([]) 立即 resolve []Promise.race([]) 永远 pending——因为没有 Promise 能落定。Promise.any([]) 立即 reject AggregateError

❌ 误区 3:「Promise.any 返回第一个成功,其他的还在跑」

其他 Promise 仍然在跑,只是结果被忽略了。和 Promise.all 一样,any 不会取消已启动的异步操作。

❌ 误区 4:「allSettled 和 all 的错误处理一样」

完全不同。all 遇到错误立即 reject,不会告诉你哪些成功了。allSettled 等所有人跑完,给你完整名单。简单说:一个只报坏消息,一个报所有的好消息和坏消息。


一句话总结

四个方法就是一个 2×2 矩阵:要全队还是只要一个?(all vs any/race)× 在乎失败还是不在乎?(all/race vs allSettled/any)。想清楚你的业务场景,自然知道选哪个。

本文由作者按照 CC BY 4.0 进行授权