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

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

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

二、手写 Promise.all

核心逻辑:

  1. 返回一个新的 Promise
  2. 遍历所有 promise,用 Promise.resolve 包一层(支持非 promise 值)
  3. 用计数器记录完成个数,按顺序存储结果
  4. 任何一个失败则直接 reject
javascript 复制代码
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);
        }
      );
    });
  });
};

测试:

javascript 复制代码
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 谁。

javascript 复制代码
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);
    });
  });
};

测试:

javascript 复制代码
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
javascript 复制代码
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'));
          }
        }
      );
    });
  });
};

测试:

javascript 复制代码
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

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


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

javascript 复制代码
// 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.allSettled 和 Promise.withResolvers。

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

相关推荐
FRing0325 分钟前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
kyriewen3 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒3 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫3 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库
jerrywus4 小时前
我用 GPT-6.1-sol 做了台会讲解的 3D 售货机:60 罐饮料,一口没喝上
前端·agent·claude
IT规划师4 小时前
编程术语中的同步与异步:从"等不等"到工程取舍
后端·面试
孟陬5 小时前
Foobar 技术周刊#159:当看不懂代码时,如何高效排障调试。脏页陷阱:一条只读查询,写入数 TB 数据。推测解码:大模型加速利器
前端·人工智能·后端
Zhao-Jichao5 小时前
AI Prompting for Everyone 课程笔记 - P17 图像生成 Imaging Generation
前端
Highcharts.js6 小时前
智能数据分析组件Orbit 正式发布与快速上手体验教程
前端·人工智能·信息可视化·数据分析系统·highcharts·orbit·智能可视化
Dream-Y.ocean6 小时前
鸿蒙PC平台星图遥感适配实战:基于 Electron 壳方案的纯 JavaScript 遥感图像处理引擎开发
javascript·electron·harmonyos