手写 Promise 静态方法:从原理到实现

在前端异步编程中,Promise 的静态方法扮演着至关重要的角色。它们为我们处理多个异步操作提供了简洁而强大的工具,让复杂的异步流程变得清晰可控。本文将深入剖析 Promise 常用静态方法的实现原理,带你一步步手写这些方法,彻底掌握它们的工作机制。

认识 Promise 静态方法

Promise 对象提供了多个静态方法,这些方法都是直接通过Promise构造函数调用的,无需实例化。它们的主要作用是对多个 Promise 实例进行组合操作,简化异步流程控制。

最常用的 Promise 静态方法包括:

  • Promise.all():等待所有 Promise 完成并收集结果
  • Promise.allSettled():等待所有 Promise 完成,无论成功失败
  • Promise.race():返回第一个完成的 Promise 结果
  • Promise.any():返回第一个成功的 Promise 结果

这些方法在实际开发中应用广泛,比如并发请求数据、设置请求超时、处理多个异步操作的结果等场景都离不开它们。

手写 Promise.resolve () 与 Promise.reject ()

作为最基础的 Promise 静态方法,resolve和reject用于快速创建处于特定状态的 Promise 实例。

Promise.resolve () 实现

Promise.resolve()的作用是返回一个处于 "已完成"(fulfilled) 状态的 Promise 实例,它接收一个参数作为完成值。如果参数本身就是一个 Promise 实例,则直接返回该实例。

javascript 复制代码
Promise.myResolve = function(value) {
  // 如果参数是Promise实例,直接返回
  if (value instanceof Promise) {
    return value;
  }
  
  // 否则返回一个新的已完成Promise
  return new Promise((resolve) => {
    resolve(value);
  });
};

这个实现非常直接:首先检查传入的值是否已经是 Promise 实例,如果是则直接返回;否则创建一个新的 Promise 并立即用该值完成它。

Promise.reject () 实现

Promise.reject()与resolve类似,但它返回的是一个处于 "已拒绝"(rejected) 状态的 Promise 实例。

javascript 复制代码
Promise.myReject = function(reason) {
  return new Promise((resolve, reject) => {
    reject(reason);
  });
};

注意reject方法不会像resolve那样检查参数是否为 Promise 实例,无论传入什么值都会作为拒绝原因。

手写 Promise.all ()

Promise.all()是最常用的 Promise 组合方法之一,它接收一个 Promise 数组 (或可迭代对象),返回一个新的 Promise。当所有输入的 Promise 都完成时,新 Promise 才会完成,结果是一个包含所有完成值的数组;如果有任何一个输入的 Promise 被拒绝,新 Promise 会立即被拒绝。

实现思路

  1. 接收一个可迭代对象作为参数,通常是数组
  1. 返回一个新的 Promise 实例
  1. 遍历输入的所有 Promise
  1. 维护一个计数器和结果数组,记录已完成的 Promise 数量和结果
  1. 当所有 Promise 都完成时,用结果数组完成新 Promise
  1. 如果有任何一个 Promise 被拒绝,立即用该原因拒绝新 Promise

代码实现

javascript 复制代码
Promise.myAll = function (promises) {
    // 返回一个新的Promise实例
    return new Promise((resolve, reject) => {
        // 存储所有成功的结果,按传入数组的顺序排列
        let results = [];
        // 记录传入的Promise总数量(用于判断是否所有Promise都已处理)
        let count = 0;
        // 记录已成功完成的Promise数量
        let fulfilledCount = 0;
        for (const p of promises) {
            let i = count++;

            // 将当前值包装为Promise(处理非Promise值的情况,如普通数字、字符串等)
            Promise.resolve(p)
                .then((data) => {
                    results[i] = data;
                    fulfilledCount++;

                    // 当所有Promise都成功时(已成功数量 === 总数量)
                    if (fulfilledCount === count) {
                        // 用结果数组resolve外部Promise
                        resolve(results);
                    }
                }, 
                // 当当前Promise失败时的回调:直接调用外部Promise的reject
                // 符合Promise.all"一错即错"的特性,立即返回第一个失败原因
                reject);
        }

        // 特殊情况:如果传入的是空数组,直接resolve空数组
        // 此时循环未执行,count保持0,直接返回空结果
        if (count === 0) {
            resolve(results);
        }
    });
};

关键细节

  • 保持结果数组的顺序与输入数组一致,即使某些 Promise 先完成
  • 只要有一个 Promise 被拒绝,就会立即触发拒绝,且只触发一次
  • 输入数组中的非 Promise 值会被Promise.resolve自动转换

手写 Promise.race ()

Promise.race()的行为类似于 "赛跑",它接收一个 Promise 数组,返回一个新的 Promise。这个新 Promise 会跟随第一个完成 (无论是完成还是拒绝) 的 Promise,采用它的结果或原因。

实现思路

  1. 接收一个可迭代对象作为参数
  1. 返回一个新的 Promise 实例
  1. 遍历输入的所有 Promise
  1. 一旦有任何一个 Promise 完成或拒绝,就用它的结果或原因处理新 Promise

代码实现

javascript 复制代码
Promise.myRace = function (promises) {
    return new Promise((resolve, reject) => {
        for (const p of promises) {
            Promise.resolve(p).then(resolve, reject);
        }
    })
}

典型应用:请求超时处理

race方法非常适合实现请求超时功能:

javascript 复制代码
// 模拟一个异步请求
function fetchData() {
  return new Promise((resolve) => {
    setTimeout(() => resolve('数据加载成功'), 3000);
  });
}
// 超时控制器
function timeout(ms) {
  return new Promise((_, reject) => {
    setTimeout(() => reject(new Error('请求超时')), ms);
  });
}
// 使用race实现超时控制
Promise.myRace([fetchData(), timeout(2000)])
  .then(console.log)
  .catch(console.error); // 会输出"请求超时"

手写 Promise.allSettled ()

Promise.allSettled()等待所有输入的 Promise 都 "落定"(settled),即无论是完成还是拒绝,都会等待所有 Promise 处理完毕。它返回的 Promise 会完成一个数组,每个元素表示对应 Promise 的结果,包含状态和值 / 原因。

实现思路

  1. 接收一个可迭代对象作为参数
  1. 返回一个新的 Promise 实例
  1. 遍历输入的所有 Promise
  1. 每个 Promise 完成或拒绝后,都记录其状态和结果
  1. 当所有 Promise 都落定后,用结果数组完成新 Promise

代码实现

javascript 复制代码
Promise.myAllSettled = function(promises){
    let results = [];
    for (const p of promises) {
        results.push(Promise.resolve(p).then((data) => ({
            status:"fulfilled",
            data
        }),
    (reason) => ({
        status:"rejected",
        reason
    })
))
    }
    return Promise.all(results);
}

应用场景

allSettled特别适合需要知道所有异步操作结果的场景,比如批量处理数据时,即使某些操作失败,也需要知道成功的部分并继续处理。

手写 Promise.any ()

Promise.any()与all相反,它等待第一个 "成功完成" 的 Promise。只要有一个输入的 Promise 成功完成,它就会用该结果完成;如果所有输入的 Promise 都被拒绝,它会用一个包含所有拒绝原因的AggregateError拒绝。

实现思路

  1. 接收一个可迭代对象作为参数
  1. 返回一个新的 Promise 实例
  1. 遍历输入的所有 Promise
  1. 只要有一个 Promise 成功完成,就用该结果完成新 Promise
  1. 如果所有 Promise 都被拒绝,收集所有原因并拒绝新 Promise

代码实现

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;
    
    if (promises.length === 0) {
      return reject(new AggregateError([], 'All promises were rejected'));
    }
    
    promises.forEach((promise) => {
      Promise.myResolve(promise).then(
        (value) => {
          // 第一个成功的Promise会触发resolve
          resolve(value);
        },
        (reason) => {
          errors.push(reason);
          rejectedCount++;
          
          // 所有Promise都被拒绝时
          if (rejectedCount === promises.length) {
            reject(new AggregateError(errors, 'All promises were rejected'));
          }
        }
      );
    });
  });
};

与 race 的区别

any和race都关注 "第一个有结果的 Promise",但有重要区别:

  • race返回第一个 "落定" 的 Promise,无论成功还是失败
  • any返回第一个 "成功完成" 的 Promise,忽略拒绝的 Promise,直到所有都拒绝才会拒绝

测试与验证

实现完成后,我们需要验证这些静态方法是否符合预期行为。以myAll为例:

ini 复制代码
// 测试Promise.myAll
const p1 = Promise.resolve(1);
const p2 = new Promise(resolve => setTimeout(() => resolve(2), 100));
const p3 = 3;
Promise.myAll([p1, p2, p3]).then(result => {
  console.log(result); // [1, 2, 3]
});
// 测试拒绝情况
const p4 = Promise.reject('出错了');
Promise.myAll([p1, p4, p2]).catch(reason => {
  console.log(reason); // '出错了'
});

同样的方式可以测试其他方法,确保它们的行为与原生 Promise 静态方法一致。

总结

通过手写这些 Promise 静态方法,我们深入理解了它们的内部工作机制:

  • all等待所有完成,或第一个拒绝
  • allSettled等待所有落定,收集所有结果
  • race返回第一个落定的结果
  • any等待第一个成功,或所有拒绝

掌握这些方法的实现原理,不仅能帮助我们更好地在项目中应用它们,还能提升我们对异步编程的理解。在实际开发中,选择合适的 Promise 静态方法可以大大简化异步流程控制,写出更清晰、更健壮的代码。

如果大家在学习和使用 Promise 的过程中有任何问题或心得,欢迎在评论区留言交流,也可以前往我的github 网站查看更多关于 Promise 的内容。

如果您觉得这篇文章对您有帮助,欢迎点赞和收藏,大家的支持是我继续创作优质内容的动力🌹🌹🌹也希望您能在😉😉😉我的主页 😉😉😉找到更多对您有帮助的内容。

  • 致敬每一位赶路人
相关推荐
旋风菠萝2 分钟前
JVM易混淆名称
java·jvm·数据库·spring boot·redis·面试
qq_427506086 分钟前
JavaScript和小程序写水印的方法示例
前端·算法·微信小程序
落雪小轩韩40 分钟前
Vue常见题目
javascript·vue.js
拾光拾趣录1 小时前
前端面试真题深度解析:从原型到安全,七道题看透核心能力
前端·面试
金山几座1 小时前
C++面试5题--6day
c++·面试
烛阴1 小时前
告别重复劳动:Gulp.js 新手入门教程
前端·javascript
JSON_L1 小时前
Vue 正在热映模块
前端·javascript·vue.js
Goboy2 小时前
Java 使用 FileOutputStream 写 Excel 文件不落盘?
后端·面试·架构
踏上青云路2 小时前
C# 闭包
java·前端·c#