从零手写简单版Promise:吃透核心原理,为进阶打基础

24 阅读7分钟

从零手写简单版Promise:吃透核心原理,为进阶打基础

从 0 开始,手写一个简单版 Promise(后续会更新完整版)。通过手动实现,帮大家搞懂 Promise 最核心的状态管理、resolve/reject 逻辑以及 then 方法的基础实现。文章会逐行拆解代码,尽量做到通俗易懂,适合前端初学者或想巩固 Promise 基础的同学阅读。

一、先明确 Promise 的核心特性(简单版)

在动手写代码之前,我们先梳理一下 Promise 最基础、最核心的特性,这是我们手写的依据:

  1. 状态不可逆:Promise 有三种状态 —— pending(等待中)、fulfilled(成功)、rejected(失败)。初始状态是 pending,一旦状态变为 fulfilled 或 rejected,就再也不能改变。
  2. 结果保存:状态变为 fulfilled 时,会保存一个成功结果(resolveValue);状态变为 rejected 时,会保存一个失败原因(rejectValue)。
  3. resolve/reject 触发状态变更:通过调用 resolve 函数将状态从 pending 改为 fulfilled,调用 reject 函数改为 rejected。
  4. then 方法接收结果:then 方法接收两个参数 —— onFulfilled(成功回调)和 onRejected(失败回调)。当状态是 fulfilled 时执行 onFulfilled,传入成功结果;状态是 rejected 时执行 onRejected,传入失败原因。

注意:本次实现的是“简单版”,暂不处理异步 executor、then 链式调用、值穿透等高级特性,后续第二版会逐步补充。我们先聚焦核心逻辑,把基础打牢。

二、逐行手写简单版 Promise

我们先给出完整的简单版代码,然后逐部分拆解讲解:

class MyPromise {
  constructor(executor) {
    // 1. 初始化状态和结果
    this.state = "pending"; 
    this.resolveValue = undefined; 
    this.rejectValue = undefined;

    // 2. 定义 resolve 函数
    const resolve = (value) => {
      // 状态不可逆,只有 pending 时才能修改
      if (this.state === "pending") {
        this.state = "fulfilled";
        this.resolveValue = value;
      }
    };

    // 3. 定义 reject 函数
    const reject = (reason) => {
      // 状态不可逆,只有 pending 时才能修改
      if (this.state === "pending") {
        this.state = "rejected";
        this.rejectValue = reason;
      }
    };

    // 4. 执行 executor,捕获异常
    try {
      executor(resolve, reject);
    } catch (error) {
      // 若 executor 执行报错,直接调用 reject
      reject(error); 
    }
  }

  // 5. 实现 then 方法
  then(onFulfilled, onRejected) {
    // 状态为 fulfilled 时,执行成功回调
    if (this.state === "fulfilled") {
      onFulfilled(this.resolveValue); 
    }

    // 状态为 rejected 时,执行失败回调
    if (this.state === "rejected") {
      onRejected(this.rejectValue); 
    }
  }
}

2.1 构造函数(constructor)核心逻辑

Promise 的构造函数接收一个 executor 函数作为参数,executor 又接收两个参数 —— resolve 和 reject 函数。构造函数的核心作用是:初始化状态、定义 resolve/reject 方法、执行 executor 并处理异常。

步骤1:初始化状态和结果
this.state = "pending"; // 初始状态:等待中
this.resolveValue = undefined; // 保存成功结果
this.rejectValue = undefined; // 保存失败原因

这三个是 Promise 实例的核心属性:

  • state:记录当前 Promise 的状态,初始值为 pending。
  • resolveValue:只有当 state 变为 fulfilled 时,才会赋值为成功结果。
  • rejectValue:只有当 state 变为 rejected 时,才会赋值为失败原因。
步骤2:定义 resolve 函数
const resolve = (value) => {
  // 关键:状态不可逆,只有 pending 时才能修改
  if (this.state === "pending") {
    this.state = "fulfilled"; // 状态改为成功
    this.resolveValue = value; // 保存成功结果
  }
};

resolve 函数的作用是“触发成功状态变更”:

  • 接收一个参数 value,即成功结果。
  • 首先判断当前状态是否为 pending —— 因为状态不可逆,只有 pending 时才能修改为 fulfilled。
  • 若状态合法,则修改 state 为 fulfilled,并将 value 保存到 resolveValue 中。
步骤3:定义 reject 函数
const reject = (reason) => {
  // 关键:状态不可逆,只有 pending 时才能修改
  if (this.state === "pending") {
    this.state = "rejected"; // 状态改为失败
    this.rejectValue = reason; // 保存失败原因
  }
};

reject 函数的作用是“触发失败状态变更”,逻辑和 resolve 类似:

  • 接收一个参数 reason,即失败原因(通常是 Error 对象或错误信息字符串)。
  • 同样判断状态为 pending 时,才修改为 rejected,并保存失败原因到 rejectValue 中。
步骤4:执行 executor 并捕获异常
try {
  executor(resolve, reject);
} catch (error) {
  reject(error); 
}

这一步是构造函数的“核心执行逻辑”:

  • 直接调用 executor 函数,并将我们定义的 resolve 和 reject 作为参数传入 —— 这样用户在使用 new Promise 时,就能拿到这两个函数来触发状态变更。
  • 用 try-catch 包裹 executor 的执行:如果 executor 执行过程中抛出异常(比如代码写错了),不需要用户手动处理,直接调用 reject 函数,将异常作为失败原因。

2.2 实现 then 方法

then 方法是 Promise 实现“异步结果传递”的核心方法(简单版先实现同步场景),它的核心逻辑是:根据当前 Promise 的状态,执行对应的回调函数,并传入保存的结果/原因。

then(onFulfilled, onRejected) {
  // 状态为 fulfilled 时,执行成功回调
  if (this.state === "fulfilled") {
    onFulfilled(this.resolveValue); 
  }

  // 状态为 rejected 时,执行失败回调
  if (this.state === "rejected") {
    onRejected(this.rejectValue); 
  }
}

简单解读:

  • then 方法接收两个参数:onFulfilled(成功时执行的回调)和 onRejected(失败时执行的回调)。
  • 判断当前 Promise 的状态:如果是 fulfilled,就调用 onFulfilled,并把之前保存的 resolveValue 传进去;如果是 rejected,就调用 onRejected,传入 rejectValue。
  • 注意:当前版本的 then 只处理“同步状态变更”(比如 executor 里直接调用 resolve/reject),如果 executor 里有异步操作(比如 setTimeout),then 方法暂时无法正确执行 —— 这部分后期会补充。

三、测试简单版 Promise

测试代码:

let p = new MyPromise((resolve, reject) => {
  let isSuccess = true;
  
  if (isSuccess) {
    resolve("成功!"); 
  } else {
    reject("失败!");  
  }
}).then(
  (result) => {
    console.log("成功回调:", result);  
  },
  (error) => {
    console.log("失败回调:", error);   
  }
);

3.1 测试成功场景

当 isSuccess = true 时,executor 里会调用 resolve("成功!"):

  1. MyPromise 实例初始化,state 为 pending。
  2. 调用 resolve("成功!"),因为 state 是 pending,所以将 state 改为 fulfilled,resolveValue 设为 "成功!"。
  3. 调用 then 方法,此时 state 已经是 fulfilled,所以执行 onFulfilled 回调,打印:成功回调: 成功!

3.2 测试失败场景

我们把 isSuccess 改为 false,测试失败场景:

let p = new MyPromise((resolve, reject) => {
  let isSuccess = false; // 改为失败
  if (isSuccess) {
    resolve("成功!"); 
  } else {
    reject("失败!");  
  }
}).then(
  (result) => {
    console.log("成功回调:", result);  
  },
  (error) => {
    console.log("失败回调:", error);   
  }
);

执行结果会打印:失败回调: 失败!,逻辑和成功场景类似,只是状态变为 rejected,执行 onRejected 回调。

3.3 测试 executor 异常场景

我们再测试一下 executor 执行报错的情况:

let p = new MyPromise((resolve, reject) => {
  // 故意抛出异常
  throw new Error("executor 执行出错了!");
}).then(
  (result) => {
    console.log("成功回调:", result);  
  },
  (error) => {
    console.log("失败回调:", error.message);   
  }
);

此时,try-catch 会捕获到异常,自动调用 reject,最终打印:失败回调:executor 执行出错了! —— 说明我们的异常处理逻辑生效了。

四、当前版本的局限性

虽然我们的简单版 Promise 能处理同步场景,但它还存在很多局限性,后期会完善一下内容:

  1. 不支持异步操作:如果 executor 里有异步操作(比如 setTimeout),then 方法会因为状态还是 pending 而无法执行回调。比如: new MyPromise((resolve) => { `` setTimeout(() => { `` resolve("异步成功"); `` }, 1000); `` }).then(result => { `` console.log(result); // 此时不会执行,因为 then 调用时 state 还是 pending ``});
  2. 不支持 then 链式调用:现在的 then 方法没有返回新的 Promise,无法实现 promise.then(...).then(...) 这样的链式调用。
  3. 不支持值穿透:当 then 方法没有传入 onFulfilled 或 onRejected 时,应该实现值穿透(比如 promise.then().then(result => console.log(result)) 应该能拿到结果)。
  4. 没有处理 onFulfilled/onRejected 不是函数的情况:规范中要求,如果这两个参数不是函数,应该忽略它们(本质也是值穿透的一部分)。

五、总结

今天我们手写了一个简单版的 Promise,核心实现了:

  • Promise 的三种核心状态及不可逆特性。
  • resolve 和 reject 函数对状态和结果的管理。
  • executor 的执行和异常捕获。
  • then 方法根据状态执行对应回调的基础逻辑。

通过手动实现,相信大家对 Promise 的核心原理有了更清晰的认识。虽然当前版本还有很多不足,但这是我们理解 Promise 进阶特性的基础。