❗实现一个 Promise(1)
符合 Promises/A+ 规范,即 then() 方法
Promise 是一个对象,用作 deferred 计算(可能是异步计算)的最终结果的 placeholder(占位符)。换句话说就是,一个 promise 代表了“异步操作”的最终结果。之所以一定是个“异步操作”,这取决于 promise 本身的执行机制,在事件循环里它属于微任务的一种。
Promise/A+
Promises/A+ 是 JavaScript promise 的开放标准,该规范详细介绍了 then 方法的行为。
作为与 promise 交互的主要方式,then 方法注册了两个回调,分别接收 promise 的正常返回值和出现异常的原因。
A. 相关术语
promise是一个对象或函数,且有一个then方法thenable是一个对象或函数,用来定义then方法value是 promise 成功状态时的值,可以是任何合法的 JavaScript 值(包括undefined,thenable, promise)reason是 promise 失败状态时的值,表示 promise 被拒绝的原因exception是使用throw语句抛出的值
在 promise 正式成为语言标准之前,JavaScript 生态系统中存在过很多种不同的 promise 实现。尽管它们的内部表示有所差异,但所有的 promise-like objects 都实现了 thenable 接口,即
then()方法。promise 也是 thenable。为了与现有的 promise 实现互操作,语言通常允许用 thenable 代替 promise。比如:
const aThenable = {
then(onFulfilled, onRejected) {
onFulfilled({
// The thenable is fulfilled with another thenable
then(onFulfilled, onRejected) {
onFulfilled(42)
}
})
}
}
Promise.resolve(aThenable) // A promise fulfilled with 42B. 要求
1. 三种状态
一个 promise 必须是以下三种状态之一:pending、fulfilled、rejected。且状态转移只能有两种:pending -> fulfilled、pending -> rejected。可以这样理解,promise 就是(承诺)其状态一旦改变,就永不可逆。

2. then() 方法
then() 方法一个 promise 必须提供一个 then 方法,用来访问 promise 的当前 value 或 reason
它接收两个参数(都是可选的):
onFulfilled方法表示状态从 pending -> fulfilled 时要执行的方法onRejected方法表示状态从 pending -> rejected 时要执行的方法
它必须返回一个 promise,为了实现
then()的链式调用
接下来,就按照规范的相关要求,一步一步实现自己的 Promise。
1. 支持构造函数和 then()
then()1.1 目标
第一版的目标,是支持以下用法:
1.2 代码实现
此时,运行目标代码,会成功输出:
另外,根据 Promises/A+ 规范的相关要求,还需要再完善下 then 方法,修改后的代码如下:
此时,运行目标代码,也可以正常输出。
1.3 实现思路
构造函数
constructor由使用者自己提供promise 的状态迁移逻辑,由其内部来维护。但调用的时机,交给使用人员
调用的时机,即
constructor()回调函数的那两个参数
then()方法根据 promise 的内部状态,执行相应的回调函数这里的
switch-case-break相当于if, if如果没有
break,则执行完当前case后还会继续执行后面的case
2. 支持异步 resolve 和 reject
2.1 目标
第二版的目标,是支持异步更新 promise 的状态。比如以下用法:
第一版的代码不支持上面的用法,如果用第一版的 MyPromise 执行,会在 1 秒后什么都不输出。因为在执行第 10 行的代码 p.then(...) 时,promise 的状态依然是 pending,所以就不会执行 then() 方法中的任何一个回调函数 onFulfilled 或 onRejected。
2.2 代码实现
要支持异步修改 promise 的状态,可以在状态是 pending 的时候就把 then() 方法的两个回调函数 onFulfilled 和 onRejected 给存起来,然后等 promise 在真正发生状态迁移的时候——即在执行传给 constructor 函数的两个参数 resolve, reject 时,再执行它们。
改造后的代码,如下:
此时,再执行上面的目标代码,就会在 1 秒后输出 x = 1。
2.3 实现思路
用箭头函数或闭包,把不同状态的“回调函数和值”对应上,并存起来
基于观察者模式,等真正发生状态的改变时,再触发相应的回调函数
需注意:在处理
pending状态时setTimeout的包裹范围setTimeout的初衷是为了确保回调参数onFulfilled(),onRejected()的异步执行而
xxxArray.push()是需要在当前then中执行的,所以在外面
3. 支持链式调用
3.1 目标
第三版的目标,是支持 then() 方法的链式调用,即 p.then().then().then()....then()。
比如以下用法:
第二版的代码不支持 then() 方法的链式调用,如果执行上面的代码,会在第 14 行报错:
3.2 代码实现
为了支持 then() 的链式调用,就需要 then() 方法返回一个新的 promise,并把上一个 then() 的返回值传过去。同时,考虑到上个 then 执行的代码是用户“自定义”的回调函数,所以需要用 try-catch 包裹下。
修改 then 方法,代码如下:
此时,再执行目标代码,就会在 1 秒后输出:
3.3 实现思路
构造一个新的 promise,同时用上一个
then()的返回值来 resolve注意事项:
在执行上一个
then代码的时候,需要 try-catch在接收上一个
then的执行结果时,只要不是异常,都会正常 resolve 给下个 then。比如:
4. 完善 then() 回调的返回值
then() 回调的返回值4.1 目标
需要注意的是,在 promise 中,有两个返回值:
then()方法本身的返回值,需要返回一个新 promise,因为要实现then()的链式调用then()方法的两个回调参数onFulfilled和onRejected,它们的返回值可以是原始值、对象或函数,甚至是另一个新 promise
实际上,这两个返回值是有关联的,那就是(本轮)onFulfilled 和 onRejected 的返回值会决定(下一轮)then() 方法的返回值。
考虑下面的用法:
如果用第三版的代码,运行以上代码,会在 1 秒后输出如下内容。此时,第 27 行的输出并不符合预期。
所以,第四版的目标,就是让 onFulfilled 和 onRejected 回调函数的返回值支持 promise。
4.2 代码实现
要完善 onFulfilled 和 onRejected 回调函数的返回值,就需要重新定义下 then() 方法中对 promise2 的 resolve 逻辑。
新增 resolvePromise 函数,代码如下:
同时,替换 then 方法里处理 prev 变量的 resolve(prev) 方法,如下:
此时,再执行目标代码,就会在 1 秒后输出:
4.3 相关说明
关于 resolvePromise() 函数的说明:
把
if (x instance of MyPromise)的逻辑,合并到了if (typeof then === 'function')里return语句,这里的主要作用是跳出函数结束执行,因为并没有地方用到它的返回值
5. 最终代码
5.1 prototype 初版
5.2 prototype 完善微任务
在实现 MyPromise.prototype.then 方法时,为了确保 onFulfilled/onRejected 回调函数的异步执行,用了宏任务 setTimeout。但这会带来延迟问题,因为两次 Event Loop 之间有时间间隔,其中浏览器约 4ms,Node.js 约 1ms。
所以,还是得用“微任务”机制,宗旨就是:在确保异步执行的同时,“尽早”地调用所有已经加入队列的回调函数。
5.3 prototype 完善封装性
5.4 class 版
6. 测试
使用 promises-aplus-tests 对最终的代码实现进行测试,看它是否符合 Promise/A+ 的规范。
6.1 新增代码
根据文档说明,修改 MyPromise.js 文件,新增以下代码:
6.2 开始测试
6.2.1 本地安装
package.json 文件的相关配置,如下:
6.2.2 npx
如果不想 npm install,也可以用 npx 直接运行,命令如下:
6.3 测试结果
872 个测试用例都成功通过。至此,我们就实现了一个符合 Promises/A+ 规范的 then() 方法。

7. 主要参考
Last updated