📖遵循 Promise/A+ 规范的 Promise 手撕
Promise 实现规范:https://promisesaplus.com/
1. 几个问题
- Promise 支持在状态改变后立即执行对应的成功或失败回调,实现类似发布订阅模式的机制。
- 当定义 Promise 的
then
方法时,若状态尚未改变,则将成功和失败回调函数注册到onResolvedCallbacks
和onRejectedCallbacks
属性中进行依赖收集。 - 在 Promise 的构造函数中,通过
resolve
和reject
方法实现状态改变,状态改变后触发所有已注册的回调,类似于发布通知并执行收集的依赖。
- 当定义 Promise 的
- Promise 支持
then
方法的链式调用与值的穿透,其实现如下:then
方法返回一个新的 Promise 对象:(1) 若当前 Promise 状态为resolved/rejected
,则根据onResolved/onRejected
回调的执行结果确定返回的 Promise 状态;(2) 若当前 Promise 状态为pending
,则将处理逻辑封装为函数,注册到onResolvedCallbacks
或onRejectedCallbacks
中,等待状态改变后执行。- 每次
then
调用都会创建一个新的回调函数,这些回调函数与传入的onResolved
和onRejected
一一对应,形成闭包。闭包确保每个回调能够正确处理对应的 Promise 状态和返回值,确保链式调用的结果能够正确传递。
2. 代码实现
2.1 类型文件
1 | /* Promise 的三种状态 */ |
2.2 MyPromise
1 | class MyPromise { |
属性
1 | // Promise 的状态 |
构造函数
1 | constructor(executor: ExecutorType) { |
实例方法
MyPromise.prototype.then ⭐
1 | /** |
MyPromise.Prototype.catch
1 | /** |
MyPromise.prototype.finally
1 | /** |
静态方法
MyPromise.resolve
1 | /** |
MyPromise.reject
1 | /** |
MyPromise.all
1 | /** |
MyPromise.race
1 | /** |
3. 规范解读
3.1 术语
promise
:有then
方法的对象或函数,同时该then
方法的行为符合 Promise/A+ 规范。thenable
:有then
方法的对象或函数。value
:任何合法的 JavaScript 值,包括undefined
、thenable
、promise
。exception
:通过throw
语句抛出的值。reason
:表示promise
被为什么被拒绝的值。
3.2 要求
promise 状态
一个 promise
的状态必须是 pending
、fulfilled
、rejected
这三种之一。
- 当
promise
的状态为pending
时,其状态可以改变为fulfilled
或rejected
。 - 当
promise
的状态为fulfilled
时,其 ①状态无法再改变,②同时有一个无法再改变的value
。 - 当
promise
的状态为rejected
时,其 ①状态无法再改变,②同时有一个无法再改变的reason
。
这里的
value
或reason
的 “无法再改变” 是指引用上的改变,而不是深层的改变,即 deep immutability。
then
方法
一个 promise
通过 then
方法访问其当前或最终的 value
或 reason
,该方法接收两个参数。then
方法的实现必须满足以下规则,
1 | promise.then(onFulfilled, onRejected) |
-
onFulfilled
和onRejected
都是可选参数- 如果
onFulfilled
不是函数,则其会被忽略 - 如果
onRejected
不是函数,则其会被忽略
- 如果
-
如果
onFulfilled
是函数- 只有当
promise
的状态是fulfilled
时,该函数才会被调用,同时promise
的value
作为该函数的第一个参数 - 当
promise
的状态不是fulfilled
时,该函数不会被调用 - 该函数最多只会调用一次
- 只有当
-
如果
onRejected
是函数- 只有当
promise
的状态是rejected
时,该函数才会被调用,同时promise
的reason
作为该函数的第一个参数 - 当
promise
的状态不是rejected
时,该函数不会被调用 - 该函数最多只会调用一次
- 只有当
-
在调用
onFulfilled
或onRejected
之前,必须确保当前的执行上下文栈为空该规则意味着:当 promise 状态改变后,
onFulfilled
或onRejected
不会立即执行,而是被放入任务队列,等待当前所有的 JavaScript 代码执行完毕后才会被调用。该规则既可以通过宏任务(macro-task,包括
setTimeout
、setImmediate
等 API)实现,也可以通过微任务(micro-task,包括MutationObserver
、process.nextTick
等 API)实现。 -
onFulfilled
或onRejected
必须以函数的形式调用,同时确保不要在函数中使用 this该规则意味着:(1)
onFulfilled
和onRejected
是纯函数,以函数形式调用,而不依赖特定的对象上下文 (2) 如果函数作为普通函数调用(没有对象上下文),在严格模式下,this
指向undefined
;非严格模式下,this
指向全局对象(浏览器中的 window or Node.js 中的 global)。 -
then
方法可能会被同一个promise
调用多次- 当
promise
的状态是fulfilled
时,所有对应的onFulfilled
回调函数将会按照其对应的then
方法的调用顺序依次执行 - 当
promise
的状态是rejected
时,所有对应的onRejected
回调函数将会按照其对应的then
方法的调用顺序依次执行
- 当
-
then
方法必须返回一个promise
1
promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled
或onRejected
返回一个值x
,则执行promise
解决程序(The Promise Resolution Procedure)[[Resolve]](promise2, x)
(这个解决程序中会检查多种情况,并将promise
的状态变为fulfilled
或rejected
) - 如果
onFulfilled
或onRejected
抛出一个exception
e
,promise2
必须以e
为reason
并使其状态为rejected
。 - 如果
onFulfilled
不是一个函数,同时promise1
的状态是fulfilled
,则promise2
必须以promise1
的value
为value
并使其状态为fulfilled
。 - 如果
onRejected
不是一个函数,同时promise1
的状态是rejected
,则promise2
必须以promise1
的reason
为reason
并使其状态为rejected
。
- 如果
promise 解决程序
promise 解决程序(The Promise Resolution Procedure)是一个抽象操作,以一个 promise
和一个 value
作为输入,表示为 [[Resolve]](promise, x)
。promise 解决程序的执行步骤如下,
这里的
promise
是then
方法返回的promise
;x
是then
方法的onFulfilled
或onRejected
回调的返回值。
-
如果
promise
和x
指向同一个对象,则以一个TypeError
为reason
使得promise
的状态为rejected
-
如果
x
是一个promise
,则采用该promise
- 如果
x
的状态是pending
,则promise
的状态也是pending
(直到x
的状态为fulfilled
或rejected
)。 - 如果
x
的状态是fulfilled
,则使promise
的状态为fulfilled
,同时以x
的value
为value
。 - 如果
x
的状态是rejected
,则使promise
的状态为rejected
,同时以x
的reason
为reason
。
- 如果
-
如果
x
是一个对象或函数,则执行以下步骤该规则实际上用于检查
x
是不是thenable
对象,并对其进行处理-
Step1. 令
then = x.then
该步骤用于存储对
x.then
的引用,这样做是为了避免对x.then
进行多次访问。在 JavaScript 中,x.then
可能是一个访问器属性(accessory property),多次访问可能会获取到不同的值,从而导致不一致的行为。因此,这样做保证了一致性。 -
Step2. 如果访问
x.then
导致抛出一个exception
e
,则以e
为reason
使promise
的状态变为rejected
。 -
Step3. 如果
then
是一个函数,则以x
作为this
调用then
,接收两个参数resolvePromise
和rejectPromise
。-
如果以一个
value
y
为参数调用resolvePromise
,则执行[[Resolve]](promise, y)
。这里的递归调用是为了兼容
thenable
,确保其可以被正确处理。通过递归调用,不断解析,直到获得最终值或状态。注意,如果
thenable
链是循环的,那么就会不断地调用同一个thenable
,永远无法获得最终的解析结果。因此可以检测这种情况,并以TypeError
为reason
使promise
的状态为rejected
。但是并不强制这种检测,源于其不常见,且增加性能开销。 -
如果以一个
reason
r
为参数调用rejectPromise
,则以r
为reason
使promise
的状态变为rejected
。拒绝的重点在于传递错误,而不是解析值。
-
如果
resolvePromise
和rejectPromise
被同时调用 or 多次调用,则以第一个调用为准,忽略剩下的。 -
如果调用
then
方法抛出一个exception
e
- 如果此时
resolvePromise
或rejectPromise
已被调用,则忽略这个exception
- 否则,以
e
为reason
使promise
的状态为rejected
。
- 如果此时
-
-
Step4. 如果
then
不是一个函数,则以x
为value
使得promise
的状态为fulfilled
。
-
-
如果
x
不是对象,也不是函数,则以x
为value
使得promise
的状态为fulfilled
。