最近看了 Promise/A+ 的规范,尝试实现了一个知足 promises-aplus-tests 测试的 Promise 类,在实现规范的过程当中,对于 Promise 自己也加深了理解,这篇文章就将个人实现过程分享出来。git
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.github
thenable
对象是一类具备 then
方法的对象或者函数。1.2 “thenable” is an object or function that defines a then method.npm
value
值,这个 value
值能够是任意合法的 JavaScript 数据类型。1.3 “value” is any legal JavaScript value (including undefined, a thenable, or a promise).promise
value
属性,Promise 内部还有一个 reason
属性,用来存放 Promise 状态变为 rejected
的缘由1.5 “reason” is a value that indicates why a promise was rejected.浏览器
根据上面的介绍,能够初步构造一个 MyPromise
类:bash
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
值。 如今,咱们能够对上面的代码进行一些测试:frontend
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
方法。dom
A promise must provide a then method to access its current or eventual value or reason.异步
then
方法接受两个参数:Promise 状态转换为 fulfilled
的回调(成功回调)和状态转换为 rejected
的回调(失败回调),这两个回调函数是可选的。async
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
方法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.
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
状态的方法加入到回调函数的队列中。
上面的代码,处理了 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 能够测试咱们实现的 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
复制代码
测试结果以下,所有经过:
完。