聊天讨论 手写 Promise.all、race、any:不到 30 行代码,解决并发异步的所有姿势

193577746(kyriewen) · June 13, 2026 · 10 hits

一、这三个方法解决什么问题?

方法 输入 输出 典型场景
Promise.all 多个 Promise 所有成功 -> 结果数组;任一失败 -> 立即 reject 多个独立请求,需要全部结果
Promise.race 多个 Promise 最先 settled 的结果(成功或失败) 超时控制、请求竞速
Promise.any 多个 Promise 任一成功 -> 该成功值;全部失败 -> AggregateError 多个备用接口,取最快成功的

二、手写 Promise.all

核心逻辑

  1. 返回一个新的 Promise
  2. 遍历所有 promise,用 Promise.resolve 包一层(支持非 promise 值)
  3. 用计数器记录完成个数,按顺序存储结果
  4. 任何一个失败则直接 reject
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }
    const results = [];
    let completed = 0;
    const total = promises.length;

    if (total === 0) {
      return resolve(results);
    }

    promises.forEach((p, index) => {
      Promise.resolve(p).then(
        (value) => {
          results[index] = value;
          completed++;
          if (completed === total) {
            resolve(results);
          }
        },
        (reason) => {
          reject(reason);
        }
      );
    });
  });
};

测试

const p1 = Promise.resolve(1);
const p2 = 2;
const p3 = new Promise((res) => setTimeout(() => res(3), 100));

Promise.myAll([p1, p2, p3]).then(console.log); // [1, 2, 3](约100ms后)

三、手写 Promise.race

核心逻辑:谁先 settled(成功或失败),就 resolve/reject 谁。

Promise.myRace = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }
    promises.forEach((p) => {
      Promise.resolve(p).then(resolve, reject);
    });
  });
};

测试

const slow = new Promise(res => setTimeout(() => res(''), 100));
const fast = new Promise(res => setTimeout(() => res(''), 20));
Promise.myRace([slow, fast]).then(console.log); // '快'

四、手写 Promise.any

核心逻辑

  1. 收集所有失败原因
  2. 只要有一个成功,就 resolve 该值
  3. 全部失败时,reject 一个 AggregateError
Promise.myAny = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) {
      return reject(new TypeError('Argument must be an array'));
    }
    const errors = [];
    let rejectedCount = 0;
    const total = promises.length;

    if (total === 0) {
      return reject(new AggregateError([], 'All promises were rejected'));
    }

    promises.forEach((p, index) => {
      Promise.resolve(p).then(
        (value) => {
          resolve(value);
        },
        (reason) => {
          errors[index] = reason;
          rejectedCount++;
          if (rejectedCount === total) {
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        }
      );
    });
  });
};

测试

const p1 = Promise.reject('错误1');
const p2 = new Promise(res => setTimeout(() => res('成功'), 50));
const p3 = Promise.reject('错误3');

Promise.myAny([p1, p2, p3]).then(console.log); // '成功'(50ms后)

五、3 个容易忽略的边界情况

边界 1:传入空数组

  • Promise.all([]) → 立即 resolve []
  • Promise.race([]) → 永远 pending(不会 resolve/reject)
  • Promise.any([]) → 立即 reject AggregateError

我们的实现已正确处理。

边界 2:传入非 Promise 值

Promise.resolve 自动包装,无需额外处理。

边界 3:数组包含已 settled 的 Promise

不影响逻辑,依然按顺序等待或竞速。


六、完整代码(可直接复制)

// Promise.all
Promise.myAll = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) reject(new TypeError('Expected array'));
    const results = [];
    let completed = 0;
    if (promises.length === 0) resolve(results);
    promises.forEach((p, i) => {
      Promise.resolve(p).then(
        v => { results[i] = v; completed++; if (completed === promises.length) resolve(results); },
        e => reject(e)
      );
    });
  });
};

// Promise.race
Promise.myRace = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) reject(new TypeError('Expected array'));
    promises.forEach(p => Promise.resolve(p).then(resolve, reject));
  });
};

// Promise.any
Promise.myAny = function(promises) {
  return new Promise((resolve, reject) => {
    if (!Array.isArray(promises)) reject(new TypeError('Expected array'));
    const errors = [];
    let rejected = 0;
    if (promises.length === 0) reject(new AggregateError([], 'All rejected'));
    promises.forEach((p, i) => {
      Promise.resolve(p).then(
        v => resolve(v),
        e => { errors[i] = e; rejected++; if (rejected === promises.length) reject(new AggregateError(errors, 'All rejected')); }
      );
    });
  });
};

七、总结

  • Promise.all:全成功才成功,一失败就失败
  • Promise.race:谁先 settled 就输出谁
  • Promise.any:一成功就成功,全失败才失败
  • 三个方法核心都是 遍历 + 计数器 + 按序存储
  • 边界情况:空数组、非 Promise 值、已 settled 项

文中代码已测试,可用于生产环境 polyfill。下一篇写 Promise.allSettledPromise.withResolvers

讨论:你在项目中用过 Promise.any 吗?一般用在哪里?欢迎评论区分享。

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.