最近看了 Promise/A+ 的规范,尝试实现了一个满足 promises-aplus-tests 测试的 Promise 类,在实现规范的过程中,对于 Promise 本身也加深了理解,这篇文章就将我的实现过程分享出来。
- 本文的代码仓库在这里,欢迎 Star~。
前置知识
- Promise 是用来解决异步问题的一个方案,相当于异步操作的占位符。
- 每个 Promise 只有三种状态:
pending
,fulfilled
和rejected
,状态只能从pending
转移到fulfilled
或者rejected
,一旦状态变成fulfilled
或者rejected
,就不能再更改其状态。
2.1.1 When pending, a promise:
2.1.1.1 may transition to either the fulfilled or rejected state.
2.1.2 When fulfilled, a promise:
2.1.2.1 must not transition to any other state.
2.1.2.2 must have a value, which must not change.
2.1.3 When rejected, a promise:
2.1.3.1 must not transition to any other state.
2.1.3.2 must have a reason, which must not change.
-
thenable
对象是一类具有then
方法的对象或者函数。
1.2 “thenable” is an object or function that defines a then method.
- 每个 Promise 内部都有一个
value
值,这个value
值可以是任意合法的 JavaScript 数据类型。
1.3 “value” is any legal JavaScript value (including undefined, a thenable, or a promise).
- 除了
value
属性,Promise 内部还有一个reason
属性,用来存放 Promise 状态变为rejected
的原因
1.5 “reason” is a value that indicates why a promise was rejected.
构造 MyPromise 类
根据上面的介绍,可以初步构造一个 MyPromise
类:
class MyPromise {
constructor(exector) {
this.status = MyPromise.PENDING;
this.value = null;
this.reason = null;
this.initBind();
this.init(exector);
}
initBind() {
// 绑定 this
// 因为 resolve 和 reject 会在 exector 作用域中执行,因此这里需要将 this 绑定到当前的实例
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
init(exector) {
try {
exector(this.resolve, this.reject);
} catch (err) {
this.reject(err);
}
}
resolve(value) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.FULFILLED;
this.value = value;
}
}
reject(reason) {
if (this.status === MyPromise.PENDING) {
this.status = MyPromise.REJECTED;
this.reason = reason;
}
}
}
// 2.1 A promise must be in one of three states: pending, fulfilled, or rejected.
MyPromise.PENDING = "pending"
MyPromise.FULFILLED = "fulfilled"
MyPromise.REJECTED = "rejected"
exector
是创建 Promise 对象时传递给构造函数的参数,resolve
和 reject
方法分别用来将 Promise 对象的状态由 pending
转换成 fulfilled
和 rejected
,并向 Promise 对象中写入相应的 value
或者 reason
值。
现在,我们可以对上面的代码进行一些测试:
const p1 = new MyPromise((resolve,reject) => {
const rand = Math.random();
if(rand > 0.5) resolve(rand)
else reject(rand)
})
console.log(p1)
// MyPromise {status: "fulfilled", value: 0.9121690746412516, reason: null, resolve: ƒ, reject: ƒ}
上面的代码,已经可以让 Promise 对象实现状态变换,并保存 value
或者 reason
值,但单纯完成状态的转换和保存值是不够的,作为异步的解决方案,我们还需要让 Promise 对象在状态变换后再做点什么。
这就需要我们为 Promise 对象再提供一个 then
方法。
A promise must provide a then method to access its current or eventual value or reason.
then 方法
then
方法接受两个参数:Promise 状态转换为 fulfilled
的回调(成功回调)和状态转换为 rejected
的回调(失败回调),这两个回调函数是可选的。
A promise’s then method accepts two arguments:
promise.then(onFulfilled, onRejected)
2.2.1 Both onFulfilled and onRejected are optional arguments:
2.2.1.1 If onFulfilled is not a function, it must be ignored.
2.2.1.2 If onRejected is not a function, it must be ignored.
下面为 MyPromise 类添加一个 then
方法:
...
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}
onRejected = typeof onRejected === "function" ? onRejected : () => {}
if (this.status === MyPromise.FULFILLED) {
try{
onFulfilled(this.value)
}catch(e){
onRejected(e)
}
}
if (this.status === MyPromise.REJECTED) {
try{
onRejected(this.reason);
}catch(e){
onRejected(e)
}
}
}
...
下面测试一下 then
方法:
const p1 = new MyPromise((resolve) => resolve("Success"))
p1.then(data => console.log(data))
// Success
这里,我们初步完成了 MyPromise 类的 then
方法。
但仔细看上面的 then
方法和 MyPromise 类的实现,还存在几个缺陷:
- 只处理了状态为
fulfilled
和rejected
的情况,没有处理状态为pending
的情况 -
onFulfilled
和onRejected
方法是同步执行的,也就是说,调用then
方法,就会执行onFulfilled
和onRejected
方法 - MyPromise 类中的
resolve
和reject
方法也是同步的,这意味着会出现下面的情况:
console.log("START")
const p2 = new MyPromise(resolve => resolve("RESOLVED"))
console.log(p2.value)
console.log("END")
输出结果为:
START
RESOLVED
END
按照规范,Promise 应该是异步的。
2.2.4
onFulfilled
oronRejected
must not be called until the execution context stack contains only platform code.
规范还指出了,应该使用 setTimeout
或 setImmediate
这样的宏任务方式,或者 MutationObserver
或 process.nextTick
这样的微任务方式,来调用 onFulfilled
和 onRejected
方法。
Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that
onFulfilled
andonRejected
execute asynchronously, after the event loop turn in whichthen
is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such assetTimeout
orsetImmediate
, or with a “micro-task” mechanism such asMutationObserver
orprocess.nextTick
. Since the promise implementation is considered platform code, it may itself contain a task-scheduling queue or “trampoline” in which the handlers are called.
- MyPromise 对象的状态不能被异步的改变,换句话说,无法满足
exector
方法为异步的情况:
const p3 = new MyPromise(resolve => setTimeout(() => resolve("RESOLVED")));
p3.then(data => console.log(data))
// 无输出
这里无输出的原因是在实现 then
方法的时候,没有处理状态为 pending
的情况,那么在 pending
状态下,对于 then
方法的调用,不会有任何的响应,因此在 then
方法中,对于 pending
状态的处理也很重要。
下面就针对上面出现的问题,做一些改进。
改进
首先,应该确保 onFulfilled
和 onRejected
方法,以及 resolve
和 reject
方法是异步调用的:
...
resolve(value) {
if (this.status === MyPromise.PENDING) {
setTimeout(() => {
this.status = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallback.forEach(cb => cb(this.value));
})
}
}
reject(reason) {
if (this.status === MyPromise.PENDING) {
setTimeout(() => {
this.status = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallback.forEach(cb => cb(this.reason));
})
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}
onRejected = typeof onRejected === "function" ? onRejected : () => {}
if (this.status === MyPromise.FULFILLED) {
setTimeout(() => {
try{
onFulfilled(this.value)
}catch(e){
onRejected(e)
}
})
}
if (this.status === MyPromise.REJECTED) {
setTimeout(() => {
try{
onRejected(this.reason);
}catch(e){
onRejected(e)
}
})
}
}
...
然后,需要在 MyPromise 类中,在设置两个队列:onFulfilledCallback
,和 onRejectedCallback
,用来存放在 pending
状态下,调用 then
方法时传入的回调函数。
在调用 resolve
和 reject
方法时,需要将队列中存放的回调按照先后顺序依次调用(是不是感觉很像浏览器的事件环机制)。
class MyPromise {
constructor(exector) {
this.status = MyPromise.PENDING;
this.value = null;
this.reason = null;
/**
* 2.2.6 then may be called multiple times on the same promise
* 2.2.6.1 If/when promise is fulfilled, all respective onFulfilled callbacks must execute in the order of their originating calls to then
* 2.2.6.2 If/when promise is rejected, all respective onRejected callbacks must execute in the order of their originating calls to then.
*/
this.onFulfilledCallback = [];
this.onRejectedCallback = [];
this.initBind();
this.init(exector);
}
initBind() {
// 绑定 this
// 因为 resolve 和 reject 会在 exector 作用域中执行,因此这里需要将 this 绑定到当前的实例
this.resolve = this.resolve.bind(this);
this.reject = this.reject.bind(this);
}
init(exector) {
try {
exector(this.resolve, this.reject);
} catch (err) {
this.reject(err);
}
}
resolve(value) {
if (this.status === MyPromise.PENDING) {
setTimeout(() => {
this.status = MyPromise.FULFILLED;
this.value = value;
this.onFulfilledCallback.forEach(cb => cb(this.value));
})
}
}
reject(reason) {
if (this.status === MyPromise.PENDING) {
setTimeout(() => {
this.status = MyPromise.REJECTED;
this.reason = reason;
this.onRejectedCallback.forEach(cb => cb(this.reason));
})
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}
onRejected = typeof onRejected === "function" ? onRejected : () => {}
if (this.status === MyPromise.FULFILLED) {
setTimeout(() => {
try{
onFulfilled(this.value)
}catch(e){
onRejected(e)
}
})
}
if (this.status === MyPromise.REJECTED) {
setTimeout(() => {
try{
onRejected(this.reason);
}catch(e){
onRejected(e)
}
})
}
if (this.status === MyPromise.PENDING) {
// 向对了中装入 onFulfilled 和 onRejected 函数
this.onFulfilledCallback.push((value) => {
try{
onFulfilled(value)
}catch(e){
onRejected(e)
}
})
this.onRejectedCallback.push((reason) => {
try{
onRejected(reason)
}catch(e){
onRejected(e)
}
})
}
}
}
// 2.1 A promise must be in one of three states: pending, fulfilled, or rejected.
MyPromise.PENDING = "pending"
MyPromise.FULFILLED = "fulfilled"
MyPromise.REJECTED = "rejected"
进行一些测试:
console.log("===START===")
const p4 = new MyPromise(resolve => setTimeout(() => resolve("RESOLVED")));
p4.then(data => console.log(1,data))
p4.then(data => console.log(2,data))
p4.then(data => console.log(3,data))
console.log("===END===")
输出结果:
===START===
===END===
1 'RESOLVED'
2 'RESOLVED'
3 'RESOLVED'
实现链式调用
规范还规定,then
方法必须返回一个新的 Promise 对象,以实现链式调用。
2.2.7 then must return a promise.
promise2 = promise1.then(onFulfilled, onRejected);
如果 onFulfilled
和 onRejected
是函数,就用函数调用的返回值,来改变新返回的 promise2
对象的状态。
2.2.7.1 If either
onFulfilled
oronRejected
returns a valuex
, run the Promise Resolution Procedure[[Resolve]](promise2, x)
.
2.2.7.2 If eitheronFulfilled
oronRejected
throws an exceptione
,promise2
must be rejected withe
as the reason.
这里提到的 Promise Resolution Procedure,其实是针对 onFulfilled
和 onRejected
方法不同返回值的情况,来对 promise2 的状态来统一进行处理,我们暂时先忽略,后文再提供实现。
另外,如果 onFulfilled
和 onRejected
不是函数,那么就根据当前 promise 对象(promise1)的状态,来改变 promise2 的状态。
2.2.7.3 If onFulfilled is not a function and promise1 is fulfilled, promise2 must be fulfilled with the same value as promise1.
2.2.7.4 If onRejected is not a function and promise1 is rejected, promise2 must be rejected with the same reason as promise1.
由于在前面的代码,已对 onFulfilled
和 onRejected
函数进行来处理,如果不是函数的话,提供一个默认值:
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : () => {}
onRejected = typeof onRejected === "function" ? onRejected : () => {}
并且每次调用 onFulfilled
或 onRejected
方法时,都会传入当前实例的 value
或者 reason
属性,因此对于 onFulfilled
或 onRejected
不是函数的特殊情况,直接将传给它们的参数返回即可,promise2 依旧使用 onFulfilled
或 onRejected
的返回值来改变状态:
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason }
上面的方案,还顺带解决了值穿透的问题。所谓值穿透,就是调用 then
方法时,如果不传入参数,下层链条中的 then
方法还能够正常的获取到 value
或者 reason
值。
new MyPromise(resolve => setTimeout(() => { resolve("Success") }))
.then()
.then()
.then()
...
.then(data => console.log(data));
下面就根据上面的陈述,对 then
方法做进一步的改进:
···
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === "function" ? onFulfilled : value => value
onRejected = typeof onRejected === "function" ? onRejected : reason => { throw reason }
let promise2;
if (this.status === MyPromise.FULFILLED) {
return promise2 = new MyPromise((resolve,reject) => {
setTimeout(() => {
try{
const x = onFulfilled(this.value)
resolve(x);
}catch(e){
reject(e)
}
})
})
}
if (this.status === MyPromise.REJECTED) {
return promise2 = new MyPromise((resolve,reject) => {
setTimeout(() => {
try{
const x = onRejected(this.reason)
resolve(x);
}catch(e){
reject(e)
}
})
})
}
if (this.status === MyPromise.PENDING) {
return promise2 = new MyPromise((resolve,reject) => {
// 向对了中装入 onFulfilled 和 onRejected 函数
this.onFulfilledCallback.push((value) => {
try{
const x = onFulfilled(value)
resolve(x)
}catch(e){
reject(e)
}
})
this.onRejectedCallback.push((reason) => {
try{
const x = onRejected(reason)
resolve(x);
}catch(e){
reject(e)
}
})
})
}
}
···
规范规定,then
方法必须返回一个新的 Promise 对象(promise2),新的 promise2 的状态必须依赖于调用 then
方法的 Promise 对象(promise1)的状态,也就是说,必须要等到 promise1 的状态变成 fulfilled
或者 rejected
之后,promise2 的状态才能进行改变。
因此,在 then
方法的实现中,在当前的 Promise 对象(promise1)的状态为 pending
时,将改变 promise2
状态的方法加入到回调函数的队列中。
实现 resolvePromise 方法
上面的代码,处理了 onFulfilled
和 onRejected
方法的返回值的情况,以及实现了 then
方法的链式调用。
现在考虑一个问题,如果 onFulfilled
或 onRejected
方法返回的是一个 Promise 对象,或者是具有 then
方法的其他对象(thenable
对象),该怎么处理呢?
规范中提到,对于 onFulfilled
或 onRejected
的返回值的,提供一个 Promise Resolution Procedure 方法进行统一的处理,以适应不同的返回值类型。
2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).
我们将这个方法命名为 resolvePromise
方法,将其设计为 MyPromise 类上的一个静态方法。
resolvePromise
静态方法的作用,就是根据 onFulfilled
或 onRejected
不同的返回值(x
)的情况,来改变 then
方法返回的 Promise 对象的状态。
可以这样理解:我们将改变 promise2 对象的状态的过程,移动到了 resolvePromise
方法中,以便处理更多的细节问题。
下面是 resolvePromise
方法的实现:
MyPromise.resolvePromise = (promise2,x,resolve,reject) => {
let called = false;
/**
* 2.3.1 If promise and x refer to the same object, reject promise with a TypeError as the reason.
*/
if(promise2 === x){
return reject(new TypeError("cannot return the same promise object from onfulfilled or on rejected callback."))
}
if(x instanceof MyPromise){
// 处理返回值是 Promise 对象的情况
/**
* new MyPromise(resolve => {
* resolve("Success")
* }).then(data => {
* return new MyPromise(resolve => {
* resolve("Success2")
* })
* })
*/
if(x.status === MyPromise.PENDING){
/**
* 2.3.2.1 If x is pending, promise must remain pending until x is fulfilled or rejected.
*/
x.then(y => {
// 用 x 的 fulfilled 后的 value 值 y,去设置 promise2 的状态
// 上面的注视,展示了返回 Promise 对象的情况,这里调用 then 方法的原因
// 就是通过参数 y 或者 reason,获取到 x 中的 value/reason
// 拿到 y 的值后,使用 y 的值来改变 promise2 的状态
// 依照上例,上面生成的 Promise 对象,其 value 应该是 Success2
// 这个 y 值,也有可能是新的 Promise,因此要递归的进行解析,例如下面这种情况
/**
* new Promise(resolve => {
* resolve("Success")
* }).then(data => {
* return new Promise(resolve => {
* resolve(new Promise(resolve => {
* resolve("Success3")
* }))
* })
* }).then(data => console.log(data))
*/
// 总之,使用 “return”链中最后一个 Promise 对象的状态,来决定 promise2 的状态
MyPromise.resolvePromise(promise2, y, resolve, reject)
},reason => {
reject(reason)
})
}else{
/**
* 2.3 If x is a thenable, it attempts to make promise adopt the state of x,
* under the assumption that x behaves at least somewhat like a promise.
*
* 2.3.2 If x is a promise, adopt its state [3.4]:
* 2.3.2.2 If/when x is fulfilled, fulfill promise with the same value.
* 2.3.2.4 If/when x is rejected, reject promise with the same reason.
*/
x.then(resolve,reject)
}
/**
* 2.3.3 Otherwise, if x is an object or function,
*/
}else if((x !== null && typeof x === "object") || typeof x === "function"){
/**
* 2.3.3.1 Let then be x.then.
* 2.3.3.2 If retrieving the property x.then results in a thrown exception e, reject promise with e as the reason.
*/
try{
// then 方法可能设置了访问限制(setter),因此这里进行了错误捕获处理
const then = x.then;
if(typeof then === "function"){
/**
* 2.3.3.2 If retrieving the property x.then results in a thrown exception e,
* reject promise with e as the reason.
*/
/**
* 2.3.3.3.1 If/when resolvePromise is called with a value y, run [[Resolve]](promise, y).
* 2.3.3.3.2 If/when rejectPromise is called with a reason r, reject promise with r.
*/
then.call(x,y => {
/**
* If both resolvePromise and rejectPromise are called,
* or multiple calls to the same argument are made,
* the first call takes precedence, and any further calls are ignored.
*/
if(called) return;
called = true;
MyPromise.resolvePromise(promise2, y, resolve, reject)
},r => {
if(called) return;
called = true;
reject(r);
})
}else{
resolve(x)
}
}catch(e){
/**
* 2.3.3.3.4 If calling then throws an exception e,
* 2.3.3.3.4.1 If resolvePromise or rejectPromise have been called, ignore it.
* 2.3.3.3.4.2 Otherwise, reject promise with e as the reason.
*/
if(called) return;
called = true;
reject(e)
}
}else{
// If x is not an object or function, fulfill promise with x.
resolve(x);
}
}
在我实现规范的规程中,这个 resolvePromise
最最难理解的,主要是 return 链这里,因为想不到具体的场景。我将具体的场景通过注释的方式写在上面的代码中了,同样迷惑的童鞋可以看看。
进行 promises-aplus-tests 测试
通过 promises-aplus-tests 可以测试我们实现的 Promise 类是否满足 Promise/A+ 规范。
进行测试之前,需要为 promises-aplus-tests 提供一个 deferred
的钩子:
MyPromise.deferred = function() {
const defer = {}
defer.promise = new MyPromise((resolve, reject) => {
defer.resolve = resolve
defer.reject = reject
})
return defer
}
try {
module.exports = MyPromise
} catch (e) {
}
安装并运行测试:
npm install promises-aplus-tests -D
npx promises-aplus-tests promise.js
测试结果如下,全部通过:
至此,我们实现了一个完全满足 Promise/A+ 规范的 Promise,本文的代码仓库在这里,欢迎 Star~。
完。