实现一个最简单的Promise

中文: 实现一个最基础的 Promise,只支持:

English: Implement a minimal Promise that supports:


分析:

要结合Promise的定义、作用、用法,还有class的用法来理解。参考:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

1、定义:Promise是JS中用于处理异步操作的一种对象。可以使用prototype的方式来实现,但语法太琐碎,所以最好使用class来实现,它语法直观、可读性强。

2、Promise的构造函数怎么写?还是要参考文档:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise

image-20260410104106045

这是最难理解的部分,要根据Promise的用法来做。

仔细看executor的定义。①他是一个函数。②它接收两个参数,resolveFunc和rejectFunc。③它返回的是一个promise对象,这个现在不用管,只需要返回一个函数或者reject(err)即可。

疑问:

① 既然 new Promise(executor)executor 函数接收 (resolve, reject) 参数,为什么resolveFunc和rejectFunc需要在constructor里面来直接实现?为什么不是 executor 函数接收 (resolve, reject) 参数传递过来的?

主要原因有两点:

1、它们是 Promise 内部状态的“钥匙”

resolvereject 的唯一任务就是:修改这个 Promise 实例的内部状态(从 pending 变为 fulfilledrejected)。

2、控制权的安全移交(Privilege Design)

Promise 设计遵循一种“特权模式”:只有创建 Promise 的人,才有权决定它什么时候成功或失败。

如果你尝试从外部传入这两个函数,代码会变得逻辑混乱。比如:

你会发现 myResolve 根本不知道它要“解决”哪一个 Promise 实例。

总结

constructor 内部实现这两个函数,本质上是 Promise 在向你发出邀请:

“嘿,我为你这个实例专门定制了两个改状态的工具,我现在通过 executor 的参数交给你。只有你能用它们来告诉我任务结束了,别人都改不了我。”

这就是为什么我们在实现 MyPromise 时,必须在 constructor 内部手动写好 const resolve = ...,然后把这个定义好的函数引用传给 executor

理解了这个设计,你也就理解了 Promise 是如何保证状态安全和不可逆的。

 

② contructor里面很抽象,用到了哪些js技术?

1、闭包 (Closure) —— 最关键的基石

哪里用了:constructor 内部定义的 const resolveconst reject 为什么难懂: 这两个函数在 constructor 执行完之后,依然能“记住”并操作 this.statethis.value 等变量。 通俗解释: 这就像你在家里(constructor)留了两个遥控器给客人(executor)。即便你离开了家,客人按下遥控器,依然能控制你家里的电视机。

2、箭头函数 (Arrow Functions) 与 this 绑定

哪里用了: const resolve = (value) => { ... } 为什么难懂: 如果你用普通的 function 定义,当它在 executor 里被调用时,this 可能会指向全局对象或 undefined 通俗解释: 箭头函数能自动捕捉定义时所在的 this。它确保了无论 resolve 被丢到哪里执行,它改的一定是当前这个 Promise 实例的状态。

3、控制权反转 (Inversion of Control)

哪里用了: executor(resolve, reject) 为什么难懂: 通常是我们调用系统的函数,但这里是 Promise 调用你传入的函数 通俗解释: 这叫“不要打电话给我们,我们会给你打电话”。Promise 准备好了工具(resolve/reject),然后通过回调把工具递到你手里,让你在自己的逻辑里决定什么时候用。

4、状态机模型 (State Machine)

哪里用了: this.state = "pending" 以及改变状态时的 if 判断。 为什么难懂: 因为它要求逻辑是“原子化”的。 通俗解释: Promise 像是一个有三种颜色的灯。你的逻辑确保了灯只能从“灰色(pending)”变到“绿色(fulfilled)”或“红色(rejected)”,且一旦变色,就再也按不动开关了。

5、异常处理 (Try...Catch)

哪里用了: try { executor(...) } catch (err) { reject(err) } 为什么难懂: 它捕捉的是外部传入的代码。 通俗解释: 这是一个“安全气囊”。万一用户在 executor 里的代码写错了(比如变量名写错),MyPromise 不会直接让整个程序崩溃,而是优雅地自动切换到“失败状态”。

 

③ executor的作用是什么?(这个要重点理解)

可以结合实际使用来理解,最经典的就是获取api接口数据之后做下一步的动作。

这个例子可以很好的说明,Promise能够让你不用关心任务什么时候结束(因为用到Promise的地方,可能都会有异步操作),在new的时候告诉它执行什么任务,之后只需要通过 then 告诉它“之前的任务结束之后该干嘛”。

 

代码如下:

下面是对代码的解释:

  1. Promise 的定义:它是一个“状态机”

在你的代码里,class MyPromise 就是在定义这个状态机。

  1. 什么是 Executor(执行器)?

executor 是连接“Promise 内部逻辑”和“外部业务逻辑”的桥梁。

 

  1. 用法:如何使用这个最简版?

假设我们要模拟一个同步的业务逻辑(因为你这个版本暂不支持异步):

  1. 结合你的代码看流程

  2. 初始化:当你 new MyPromise 时,构造函数运行,状态设为 pending

  3. 执行任务:构造函数里的 executor 立即执行。

  4. 状态变更

    • executor 里你调用了 resolve("数据")
    • 这会触发你代码里定义的 const resolve = (value) => { ... }
    • 关键点:此时 this.statepending 变成了 fulfilledthis.value 存下了 "数据"
  5. 处理结果

    • 当你调用 .then() 时,then 内部会判断 if (this.state === "fulfilled")
    • 因为状态已经变了,它会立刻执行你传给 then 的第一个参数(函数),并把 this.value 传进去。

总结

3、.then()方法最多接收两个参数。第一个参数是 Promise 兑现时的回调函数,第二个参数是 Promise 拒绝时的回调函数。https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise/then

image-20260410104549746

image-20260410104614907

但是在这个例子中,我们返回的不是一个Promise对象,而是直接执行then,所以里面只需要根据state状态,来判断是执行onFulfilled还是onRejected函数,并传递相应的参数即可。


测试:

针对你实现的那个最简版 MyPromise,我们可以通过以下三个测试案例来验证它的核心逻辑(同步执行、状态锁定、异常捕获)。

你可以将这些代码直接粘贴到你的 MyPromise 类下方运行:

  1. 测试案例:正常同步调用(验证基本流程)

这是最基础的场景,验证 resolve 是否能正常改变状态并触发 then

image-20260410140251551

  1. 测试案例:状态不可逆性(验证状态锁定)

Promise 的规范要求状态一旦改变就不能再变。这个案例验证你的 if (this.state === "pending") 判断是否生效。

image-20260410140341155

  1. 测试案例:异常捕获(验证 try...catch)

验证当 executor 内部发生代码错误时,Promise 是否能自动转为 rejected 状态。

image-20260410140403640

  1. 异步测试

为了让你看清这个版本的局限性,你可以运行下面这个案例。你会发现控制台什么都不会输出

总结

你的最简版代码已经成功实现了:

 

在上面的Promise基础上实现异步

从上面的测试4可以看出,如果任务是异步的,then执行的时候状态还是pending,现有的then函数里面没有对pending进行处理。该怎么处理呢?

Question(核心)就是:如果 state 是 pending,then 应该做什么?

中文: 如果 state 是 pending,then 应该把回调保存起来,等待之后执行

English: If the state is pending, then should store the callbacks and execute them later.

中文: Promise 本质是一个“发布-订阅模型”,then 是订阅,resolve/reject 是发布。

English: Promise is essentially a publish-subscribe model: then subscribes, and resolve/reject publish.


新增两个队列:

在then中,处理state为“pending”时的逻辑,也就是store the callbacks。

修改resolve和reject,当异步任务完成后,执行resolve或者reject的时候,将队列里面的callbacks取出来执行。

完整代码:


测试:

image-20260410145034988

注意执行流程:

executor里面先执行setTimeout,state没有变化;然后执行then,走then里面的state === 'pending'这段代码,然后将data => console.log(data)这段代码放到onFulfilledCallbacks中;等到1s之后,执行resolve()函数,从onFulfilledCallbacks中取出全部函数来执行。

 

实现 then 链式调用 + 返回新 Promise

要求:


then 必须返回一个新的 Promise,是为了实现:

  1. 链式调用(chainability)

每次 then 都返回一个新的 Promise,才能继续调用 then

  1. 值的传递(value propagation)

上一个 then 的返回值,会作为下一个 then 的输入

  1. 状态隔离(state isolation)

每个 then 都是一个独立的 Promise,互不影响

  1. 错误传播(error propagation)

错误可以沿着链一直传递到 catch

then 返回新的 Promise,是为了把“当前回调的执行结果”封装成一个新的异步任务,从而实现链式调用和状态传递。

 

Standard Answer (English)

then must return a new Promise to enable:

  1. Chainability
  2. Value propagation
  3. State isolation
  4. Error propagation

then returns a new Promise to wrap the result of the current callback into a new asynchronous task, enabling chaining and value propagation.


1、then的返回值必须是一个promise

2、需要处理onFulfilled、onRejected函数执行后的返回值。

接收返回值:

返回值可能有三种类型:

3、使用instanceof来判断某个对象是否属于Promise的实例

4、如果new Promise()里面的任务是异步任务,当then执行之后,不能简单的将onFulfilled、onRejected方法推送到队列中去,应该对它们进行同样的处理,然后这个then后面的.then().then()能够得到链式调用。

5、疑问:

为什么this.state === 'rejected'了,不直接onRejected(this.reason)执行呢?还要像fulfilled那样继续进行处理呢?

我被rejected的拒绝,这个翻译误导了。这个rejected并不等于是错误,而是一种状态。比如说一个任务,如果做成了接下来就执行一种方案,如果没有做成接下来就执行另外的方案,都是可以继续下去的,而且后面还会有两种方案等着,所以肯定是要继续处理的。

为了避免错误时程序停止执行,那么就需要使用try...catch来捕获错误,供接下来处理。

 

代码如下:


测试:

为了验证你这个版本的 MyPromise(支持异步和链式调用),我们需要设计几个能够体现其“进阶能力”的案例。

请先修正你代码中 reject 方法里的判断 Bug(将 if (this.state === "rejected") 改为 if (this.state === "pending")),然后运行以下测试:

  1. 测试同步链式调用(值传递)

验证 .then() 返回普通值时,能否正确传给下一个 .then()

image-20260410180116758

  1. 测试异步链式调用(Promise 嵌套拍平)

这是 Promise 最强大的地方:如果第一个 then 返回一个 新的 Promise,第二个 then 是否会等待它完成?

image-20260410180129276

  1. 测试错误冒泡(Error Propagation)

验证中间步骤报错,是否能被最后的逻辑捕获,或者传给下一个 reject

image-20260410180252955

  1. 测试同一个 Promise 挂载多个 then

验证你的 onFulfilledCallbacks 数组是否起作用(多个订阅者是否都能收到通知)。

image-20260410180331613

 

提问:为什么 Promise 要设计成:微任务(microtask)执行 then 回调?

这个问题本质在考:

👉 你能不能解释“代码执行顺序”

 

答案:

中文(面试版本)

Promise 的 then 回调被设计成微任务,是为了保证:

  1. 执行顺序更可预测
  2. 优先级高于宏任务(如 setTimeout)
  3. 保证异步操作尽快执行
  4. 避免回调被延迟到下一轮事件循环

English

then callbacks are executed as microtasks to ensure:

  1. Predictable execution order
  2. Higher priority than macrotasks
  3. Faster async execution
  4. No unnecessary delay to the next event loop

更加深入一点:

假设promise是宏任务:

promise的执行顺序就不稳定了。

 

Event Loop 执行顺序:

  1. 执行同步代码
  2. 执行所有微任务(eg: Promise),即使微任务里再产生微任务,也要继续执行
  3. 执行一个宏任务(eg: setTimeout)
  4. 再执行所有微任务
  5. 再依据1-4的规则循环