📖遵循 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方法必须返回一个promise1
promise2 = promise1.then(onFulfilled, onRejected);
- 如果
onFulfilled或onRejected返回一个值x,则执行promise解决程序(The Promise Resolution Procedure)[[Resolve]](promise2, x)(这个解决程序中会检查多种情况,并将promise的状态变为fulfilled或rejected) - 如果
onFulfilled或onRejected抛出一个exceptione,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导致抛出一个exceptione,则以e为reason使promise的状态变为rejected。 -
Step3. 如果
then是一个函数,则以x作为this调用then,接收两个参数resolvePromise和rejectPromise。-
如果以一个
valuey为参数调用resolvePromise,则执行[[Resolve]](promise, y)。这里的递归调用是为了兼容
thenable,确保其可以被正确处理。通过递归调用,不断解析,直到获得最终值或状态。注意,如果
thenable链是循环的,那么就会不断地调用同一个thenable,永远无法获得最终的解析结果。因此可以检测这种情况,并以TypeError为reason使promise的状态为rejected。但是并不强制这种检测,源于其不常见,且增加性能开销。 -
如果以一个
reasonr为参数调用rejectPromise,则以r为reason使promise的状态变为rejected。拒绝的重点在于传递错误,而不是解析值。
-
如果
resolvePromise和rejectPromise被同时调用 or 多次调用,则以第一个调用为准,忽略剩下的。 -
如果调用
then方法抛出一个exceptione- 如果此时
resolvePromise或rejectPromise已被调用,则忽略这个exception - 否则,以
e为reason使promise的状态为rejected。
- 如果此时
-
-
Step4. 如果
then不是一个函数,则以x为value使得promise的状态为fulfilled。
-
-
如果
x不是对象,也不是函数,则以x为value使得promise的状态为fulfilled。