likes
comments
collection
share

ES6系列之 模拟实现Promise

作者站长头像
站长
· 阅读数 21

前言

Promise 的基本使用可以查看阮一峰老师的《ECMAScript 6 入门》

本文主要讲解手写 Promise 核心原理以及完整的 Promises/A+ 实现。

Promise 核心

Promise 对象代表一个异步操作,它有三种状态:pending(待定)、fulfilled(已成功)和 rejected(已拒绝)。而一个 Promise 执行完成状态只会存在以下情况之一:

  • 待定 (pending) : 初始状态,既没成功,也没拒绝。
  • 已成功 (fulfilled) : 执行成功。
  • 已拒绝 (rejected) : 执行失败。

Promise 一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise 对象的状态改变,只有两种可能:从 pending 变为 fulfilled 和从 pending 变为 rejected

Promise 就好比我们去汉堡店购买汉堡,当我们跟服务员说要购买汉堡时(执行 promise),此时状态为 pending 状态;稍许片刻后,服务员说汉堡卖完了,此时状态为 rejected ;或者服务员拿来了汉堡,此时状态为 fulfilled

我们来看下 Promise 例子:

const p1 = new Promise((resolve, reject) => {
    resolve('成功')
    reject('失败')
})
console.log('p1', p1)

const p2 = new Promise((resolve, reject) => {
    reject('失败')
    resolve('成功')
})
console.log('p2', p2)

const p3 = new Promise((resolve, reject) => {
    throw('报错')
})
console.log('p3', p3)

const p4 = new Promise(() => {})
console.log('p4', p4)

const p5 = new Promise((resolve, reject) => {
    const msg = 'hello world'
})
console.log('p5', p5)

p5.then(() => {
    console.log('p5执行了then')
})

// 输出结果
p1 Promise {<fulfilled>: '成功'}
     [[Prototype]]: Promise
     [[PromiseState]]: "fulfilled"
     [[PromiseResult]]: "成功"
     
p2 Promise {<rejected>: '失败'}
     [[Prototype]]: Promise
     [[PromiseState]]: "rejected"
     [[PromiseResult]]: "失败"

p3 Promise {<rejected>: '失败'}
     [[Prototype]]: Promise
     [[PromiseState]]: "rejected"
     [[PromiseResult]]: "失败"

p4 Promise {<pending>}
     [[Prototype]]: Promise
     [[PromiseState]]: "pending"
     [[PromiseResult]]: undefined

p5 Promise {<pending>}
     [[Prototype]]: Promise
     [[PromiseState]]: "pending"
     [[PromiseResult]]: undefined

Promise {<pending>}
     [[Prototype]]: Promise
     [[PromiseState]]: "pending"
     [[PromiseResult]]: undefined

从上述例子中,我们可以得出以下知识点:

  • 执行 resolve()Promise 状态将变成 fulfilled ,即 已成功状态
  • 执行 reject()Promise 状态将变成 rejected ,即 已拒绝状态
  • Promise 只以第一次为准,状态一旦改变就不会再变。第一次成功或拒绝,状态将永久变为 fulfilledrejected
  • Promise 中存在 throw 语句,就相当于执行了 reject()
  • Promise 的初始状态为 pending
  • Promise 中没有执行 resolverejectthrow ,状态依旧为 pending
  • Promise 状态为 pending 时,将不会执行 then 回调函数

另外还需注意的是,当 Promise 对象什么都不传时,执行会报错:

const p6 = new Promise()
console.log('p6', p6)

// 输出结果 报错
Uncaught TypeError: Promise resolver undefined is not a function
   at new Promise (<anonymous>)
   at <anonymous>:1:18

这里包含一个知识点:

规定必须给 Promise 对象传入一个执行函数,否则将会报错

Promise 实现

定义初始结构

我们先来看下原生 Promise 的使用:

// 通过 new 创建 promise 实例
const promise = new Promise()

上述代码可以看出,Promise 对象通过 new 来创建一个 Promise 实例。所以我们可以使用 构造函数 或者 class 类来手写,这里我们选择使用 class 来进行创建:

class myPromise {}

上文我们提到 Promise 必须传入一个执行函数,否则将报错:

const promise = new Promise(() => {})

我们可以通过 class 的构造函数 constructor 来接收一个参数 executor (函数类型),并执行该参数:

class myPromise {
    constructor(executor) {
        executor();
    }
}

实现 resolve 和 reject

原生 Promise 传入的函数中会传入 resolvereject 两个参数:

const promise = new Promise((resolve, reject) => {})

那我们手写时也传入相同的两个参数:

class myPromise {
    constructor(executor) {
        executor(resolve, reject);
    }
}

而我们知道,原生中 resolvereject 都是以函数形式来执行的,所以我们还需定义两个函数,改写如下:

class myPromise {
    constructor(executor) {
        // 注意 class 中调用自身方法 需加上 this
        executor(this.resolve, this.reject);
    }
    resolve() {}
    reject() {}
}

状态管理

上文我们了解到 Promise 存在三个状态:pending(待定)、fulfilled(已成功)和 rejected(已拒绝),而 Promise 对象的状态改变,只有两种可能:

  • 初始状态 pending
  • pending 转为 fulfilled
  • pending 转为 rejected
ES6系列之 模拟实现Promise

因此,我们也需要定义这三个状态:

class myPromise {
    static PENDING = 'pending'; // 待定
    static FULFILLED = 'fulfilled'; // 已成功
    static REJECTED = 'rejected'; // 已拒绝
    
    constructor(executor) {
        executor(this.resolve, this.reject);
    }
    resolve() {}
    reject() {}
}

另外我们知道,原生 Promise 通过 PromiseState 来保存实例的状态属性,所以我们也可以通过 this.PromiseState 来保存实例的状态属性,该属性默认为 pending 状态:

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    
    constructor(executor) {
        this.PromiseState = myPromise.PENDING; // 默认 pending 状态
        executor(this.resolve, this.reject);
    }
    resolve() {}
    reject() {}
}

那么在执行 resolve() 函数时需判断状态是否为 pending(待定) ,如果是 pending(待定) 就把状态改为 fulfilled(成功)

class myPromise {
    // 省略
    resolve() {
        // 判断状态是否为 pending --> fulfilled
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
        }
    }
    reject() {}
}

同样,给 reject 也添加相应的判断逻辑:

class myPromise {
    // 省略
    reject() {
        // 判断状态是否为 pending --> rejected
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
        }
    }
}

执行 resolve 和 reject 可传参

原生 Promise 在执行 resolve()reject() 时可以传入一个参数:

const promise = new Promise((resolve, reject) => {
    resolve('hello world')
})

我们可以把该结果参数命名为 PromiseResult (同原生 Promise 一致),定义初始值为 null,并把 resove 和 reject 执行结果赋予该值:

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    
    constructor(executor) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        executor(this.resolve, this.reject);
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result; // 成功结果
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason; // 拒绝原因
        }
    }
}

代码写到这,我们来测下执行情况:

// 声明一个 myPromise 实例
const p1 = new myPromise((resolve, reject) => {
    resolve('测试')
})

// 输出结果 报错
Uncaught TypeError: Cannot read properties of undefined (reading 'PromiseState')
    at resolve (<anonymous>:12:18)
    at <anonymous>:26:5
    at new myPromise (<anonymous>:9:9)
    at <anonymous>:25:20

可见测试结果报错了,从报错结果分析出 PromiseState 我们已经在 constructor 中创建,而不应该是 undefined。而执行 resolvereject 方法时,PromiseState 是被 this 指向的,那说明调用 this.PromiseState 并没有调用 constructor 里的 this.PromiseState ,也就是说这里的 this 丢失了。

根据阮一峰老师的《ECMAScript 6 入门》Class 的基本语法 里, this 的指向 一节讲到,如果将这个方法(这里指 resolve)提取出来单独使用,this 会指向该方法运行时所在的环境(由于 class 内部是严格模式,所以 this 实际指向的是 undefined),从而导致找不到 PromiseState 而报错。

解决方法我们可以使用 箭头函数 或者 bind ,这里我们使用 bind 来绑定 this

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    
    constructor(executor) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        // 使用 bind 修改 this 指向   原因是 myPromise 创建实例后 单独调用 resolve 会导致 this指向丢失
        executor(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(result) {
        // 省略
    }
    reject(reason) {
        // 省略
    }
}

改造后,我们再来测试看下:

const myPromise1 = new myPromise((resolve, reject) => {
    resolve('成功')
})
console.log(myPromise1)

// 输出结果
myPromise {PromiseState: 'fulfilled', PromiseResult: '成功'}
    PromiseResult: "成功"
    PromiseState: "fulfilled"
    [[Prototype]]: Object

const myPromise2 = new myPromise((resolve, reject) => {
    reject('失败')
})
console.log(myPromise2)

// 输出结果
myPromise {PromiseState: 'rejected', PromiseResult: '失败'}
    PromiseResult: "失败"
    PromiseState: "rejected"
    [[Prototype]]: Object

我们再使用原生 Promise 看下:

const promise1 = new Promise((resolve, reject) => {
    resolve('成功')
})
console.log(promise1)

// 输出结果
Promise {<fulfilled>: '成功'}
  [[Prototype]]: Promise
  [[PromiseState]]: "fulfilled"
  [[PromiseResult]]: "成功"

const promise2 = new Promise((resolve, reject) => {
    reject('失败')
})
console.log(promise2)

// 输出结果
Promise {<rejected>: '失败'}
  [[Prototype]]: Promise
  [[PromiseState]]: "rejected"
  [[PromiseResult]]: "失败"

执行结果符合我们预期,接下来再看下 then 方法的实现。

实现 then 方法

我们先来看下原生 Promise 是如何使用的:

const promise = new Promise((resolve, reject) => {
    resolve('成功')
})

promise.then(
    result => {
        console.log(result)
    },
    reason => {
        console.log(reason)
    }
)

可以看出,then 方法是在创建完实例后再调用,并且可以传入两个参数(都是函数类型),一个状态为 fulfilled 时执行,另一个状态为 rejected 时执行,所以我们要添加以下逻辑:

  • 增加一个 then 方法
  • then 方法传入两个参数 onFulfilledonRejected ,分别代表成功和失败时的回调函数
  • 由于 Promise 状态改变是不可逆的,只能从 pending 变为 rejected 或从 pending 变为 rejected ,所以还需加上状态判断
class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    constructor(executor) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        executor(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
        }
    }
    // 改造后
    then(onFulfilled, onRejected) {
       // 状态为 fulfilled
       if (this.PromiseState === myPromise.FULFILLED) {
           // 执行成功后的回调
           onFulfilled(this.PromiseResult);
       }
       // 状态为 rejected
       if (this.PromiseState === myPromise.REJECTED) {
           // 执行失败后的回调
           onRejected(this.PromiseResult);
       }
    }
}

改造完后,我们再测试看下:

const myPromise1 = new myPromise((resolve, reject) => {
    resolve('成功')
})
myPromise1.then( 
    result => { 
        console.log(result) 
    }, 
    reason => { 
        console.log(reason) 
    } 
)

// 输出结果
"成功"

const myPromise2 = new myPromise((resolve, reject) => {
    reject('失败')
})
myPromise2.then( 
    result => { 
        console.log(result) 
    }, 
    reason => { 
        console.log(reason) 
    } 
)

// 输出结果
"失败"

执行结果达到预期,另外,原生 Promise 底层还处理了很多特殊情况,接下来我们再来聊下执行异常 throw

执行异常 throw

在实例化 Promise 后,执行函数如果抛出错误,那么在 then 回调函数的第二个参数 即 状态为 rejected 回调函数中输出错误信息。这里存在一个疑问,平时我们接收错误信息是在 catch 方法中接收,可为什么说是在 then 方法第二个参数中呢?

事实上,我们调用 Promise.catch() 是显示调用,其内部实际调用的是 .then(null, onRejected)Promise.prototype.catch() 方法是 .then(null, rejection).then(undefined, rejection) 的别名,用于指定发生错误时的回调函数。

Promise.then(
      val => console.log('fulfilled:', val)
  )
  .catch(
      error => console.log('rejected', error)
  );

// 等同于
Promise.then(
    null,
    error => { console.log(error) }
) 

// 例子
const promise = new Promise((resolve, reject) => {
    throw new Error('test')
})

promise.catch(error => {
    console.log(error)
})

// 输出结果
Error: test

上述案例等价于:

// 第一种
const promise = new Promise((resolve, reject) => {
    reject(new Error('test'))
})

// 第二种
const promise = new Promise((resolve, reject) => {
    try {
        throw new Error('test')
    } catch (error) {
        reject(error)
    } 
})

promise.catch(error => {
    console.log(error)
})

// 输出结果
Error: test

上述写法得出,reject 方法作用等同于抛出错误,因此我们就可以用 try/catch 来处理异常。另外,平时写法更推荐使用 catch 来捕获错误,而不推荐 then 方法第二个回调函数,原因是可以捕获前面 then 方法执行中的错误,也更接近同步的写法( try/catch

接下来,我们先按原来代码测试一下 throw

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    constructor(executor) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        executor(this.resolve.bind(this), this.reject.bind(this));
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
        }
    }
    then(onFulfilled, onRejected) {
       if (this.PromiseState === myPromise.FULFILLED) {
           onFulfilled(this.PromiseResult);
       }

       if (this.PromiseState === myPromise.REJECTED) {
           onRejected(this.PromiseResult);
       }
    }
}

const myPromise1 = new myPromise((resolve, reject) => {
    throw new Error('失败')
})

myPromise1.then( 
    result => { 
        console.log('fulfilled:', result) 
    }, 
    reason => { 
        console.log('rejected:', reason) 
    } 
)

// 输出结果
Uncaught Error: 失败

可以看出执行报错了,并没有捕获到错误信息,也没输出错误内容。所以我们可添加以下逻辑:

  • 在执行 resolve()reject() 之前用 try/catch 进行判断
  • 如果没有报错,就正常执行 resolve()reject() 方法
  • 否则就把错误信息传入 reject() 方法,并直接执行 reject()
class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    constructor(executor) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        // 执行 resolve 和 reject 之前使用 try/catch , 如果报错 通过 catch 来捕获错误
        try {
            executor(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
            this.reject(error);
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
        }
    }
    then(onFulfilled, onRejected) {
       if (this.PromiseState === myPromise.FULFILLED) {
           onFulfilled(this.PromiseResult);
       }

       if (this.PromiseState === myPromise.REJECTED) {
           onRejected(this.PromiseResult);
       }
    }
}

改造后,我们再测试一下:

const myPromise1 = new myPromise((resolve, reject) => {
    throw new Error('失败')
})

myPromise1.then( 
    result => { 
        console.log('fulfilled:', result) 
    }, 
    reason => { 
        console.log('rejected:', reason) 
    } 
)

// 输出结果
rejected: Error: 失败

可以看出,执行结果没有报错,并成功捕获了错误信息并输出。还需注意的是,catch 方法中不需要给 reject() 方法进行 this 绑定,因为这里是直接执行,而不是创建实例后再执行。对于构造函数constructor 中要执行的 executor() 方法里的 reject 只是作为参数传入,并不执行,只有创建实例后调用 reject() 方法时才执行,此时 this 的指向已改变,所以想要正确调用 myPromisereject() 方法就要通过 bind 改变 this 指向。

参数校验

原生 Promise 规定 then 方法中的两个参数如果不是 函数 则直接忽略,我们来看下下面的代码:

const promise = new Promise((resolve, reject) => {
    resolve('测试')
})

promise.then(
    undefined,
    reason => {
        console.log('rejected:', reason)
    }
)

// 输出结果
Promise {<fulfilled>: '测试'}
  [[Prototype]]: Promise
  [[PromiseState]]: "fulfilled"
  [[PromiseResult]]: "测试"

可以看出,执行并没有报错。我们再执行下手写 myPromise 的代码:

class myPromise {
    // 省略
}

const promise1 = new myPromise((resolve, reject) => {
    resolve('测试')
})

promise1.then(
    undefined,
    reason => {
        console.log('rejected:', reason)
    }
)

// 输出结果
Uncaught TypeError: onFulfilled is not a function

结果 Uncaught TypeError: onFulfilled is not a function 报错了,这并不是我们想要的。我们再来看下之前手写 then 部分代码:

then(onFulfilled, onRejected) {
    if (this.PromiseState === myPromise.FULFILLED) {
        onFulfilled(this.PromiseResult);
    }

    if (this.PromiseState === myPromise.REJECTED) {
        onRejected(this.PromiseResult);
    }
}

上述代码逻辑分别执行成功或失败后的回调方法,但是我们不想去修改原逻辑,那么只能把参数类型不是为函数时修改为函数。

原生 Promise 规范如果 onFulfilled 和 onRejected 不是函数,则直接忽略。所谓忽略,并不是什么都不做。对于 onFulfilled 来说 就是将 value 原封不动的返回,对于 onRejected 来说就是返回 reasononRejected 因为是错误分支,所以我们返回 reason 应该 throw 抛出一个错误。

改造如下:

then(onFulfilled, onRejected) {
    // onFulfilled 如果为函数类型则直接赋值该函数  否则将 value 原封不动返回 
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
    // onRejected 如果为函数类型则直接赋值该函数  否则将 throw 抛出错误
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
    
    if (this.PromiseState === myPromise.FULFILLED) {
        onFulfilled(this.PromiseResult);
    }

    if (this.PromiseState === myPromise.REJECTED) {
        onRejected(this.PromiseResult);
    }
}

改造完后,我们再来测试一下:

class myPromise {
    // 省略
}

const promise1 = new myPromise((resolve, reject) => {
    resolve('测试')
})

promise1.then(
    undefined,
    reason => {
        console.log('rejected:', reason)
    }
)

console.log(promise1)

// 输出结果
myPromise {PromiseState: 'fulfilled', PromiseResult: '测试'}
    PromiseResult: "测试"
    PromiseState: "fulfilled"
    [[Prototype]]: Object

结果没有报错,达到预期。另外还需说明下,这里成功后的返回值,需要在下次 then 成功回调中获取到,这里留个悬念,下文 then 方法链式调用中会讲解到。

当前实现的完整代码:

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    constructor(executor) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        try {
            executor(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
            this.reject(error);
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
        }
    }
    then(onFulfilled, onRejected) {
       onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
       onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
       
       if (this.PromiseState === myPromise.FULFILLED) {
           onFulfilled(this.PromiseResult);
       }

       if (this.PromiseState === myPromise.REJECTED) {
           onRejected(this.PromiseResult);
       }
    }
}

实现异步

我们先来看下原生 Promise 异步的使用:

console.log(1);

const promise = new Promise((resolve, reject) => {
    console.log(2);
    resolve('成功');
})

promise.then(
    result => {
        console.log('fulfilled:', result);
    },
    reason => {
        console.log('rejected:', reason)
    }
)

console.log(3);

// 输出结果
1
2
3
fulfilled: 成功

来看下执行顺序:

  • 首先执行 console.log(1) 输出结果 1
  • 接着创建 Promise 实例,并输出 2,这里依旧是同步
  • 然后执行 resolve 修改结果值
  • 接着执行 promise.then 会进行异步操作,于是执行 console.log(3) 输出结果 3
  • 等执行栈中都清空后,再执行 promise.then 里的内容,最后输出 fulfilled: 成功

我们用同样的代码在手写 myPromise 上测试:

class myPromise {
    // 省略
}

console.log(1);

const p1 = new myPromise((resolve, reject) => {
    console.log(2);
    resolve('成功');
})

p1.then(
    result => {
        console.log('fulfilled:', result);
    },
    reason => {
        console.log('rejected:', reason)
    }
)

console.log(3);

// 输出结果
1
2
fulfilled: 成功
3

可以看出只有 fulfilled: 成功3 顺序不对,其实问题很简单,就是我们刚说的没有设置异步执行。

我们再来看下 Promises/A+ 规范是怎么定义的:

规范 2.2.4

onFulfilled or onRejected must not be called until the execution context stack contains only platform code.[3.1].

译文:

onFulfilled 和 onRejected 只有在 执行环境 堆栈仅包含平台代码时才可被调用 注1

规范对 2.2.4 做了注释:

3.1 Here “platform code” means engine, environment, and promise implementation code. In practice, this requirement ensures that onFulfilled and onRejected execute asynchronously, after the event loop turn in which then is called, and with a fresh stack. This can be implemented with either a “macro-task” mechanism such as setTimeout or setImmediate, or with a “micro-task” mechanism such as MutationObserver or process.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.

译文:

3.1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。这个事件队列可以采用“宏任务(macro-task)”机制,比如setTimeout 或者 setImmediate; 也可以采用“微任务(micro-task)”机制来实现, 比如 MutationObserver 或者process.nextTick

由于 promise 的实施代码本身就是平台代码(译者注:即都是 JavaScript),故代码自身在处理程序时可能已经包含一个任务调度队列或『跳板』)。

接下来,我们使用规范中讲到的宏任务 setTimeout 来进行改造:

class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
       onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
       onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
       
       if (this.PromiseState === myPromise.FULFILLED) {
           // 使用 setTimeout 设置异步  也可以使用 queueMicrotask
           setTimeout(() => {
               onFulfilled(this.PromiseResult);
           })
       }

       if (this.PromiseState === myPromise.REJECTED) {
           // 使用 setTimeout 设置异步  也可以使用 queueMicrotask
           setTimeout(() => {
               onRejected(this.PromiseResult);
           })
       }
    }
}

// 执行
console.log(1);

const p1 = new myPromise((resolve, reject) => {
    console.log(2);
    resolve('成功');
})

p1.then(
    result => {
        console.log('fulfilled:', result);
    },
    reason => {
        console.log('rejected:', reason)
    }
)

console.log(3);

// 输出结果
1
2
3
fulfilled: 成功

可见,执行结果达到预期。另外还有种情况,我们在原生 Promise 中添加 setTimeout,让 resolve 也异步执行,那么就会存在这样一个问题,resolvethen 都是异步的,究竟谁会先执行呢?我们来看下以下案例:

console.log(1);
const promise = new Promise((resolve, reject) => {
    console.log(2);
    setTimeout(() => {
        resolve('成功');
        console.log(4);
    });
})
promise.then(
    result => {
        console.log('fulfilled:', result);
    },
    reason => {
        console.log('rejected:', reason)
    }
)
console.log(3);

// 输出结果
1
2
3
4
fulfilled: 成功

这里涉及到浏览器的事件循环,而异步任务又分为:微任务(micro task)宏任务(macro task) ,这里不展开讲解,具体的可以查阅相关资料。我们只需要记住当 当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。

我们再来分析下上述代码执行顺序:

  • 第一步,执行 console.log(1) 输出结果 1。
  • 第二步,创建 Promise 实例,执行 console.log(2) 输出结果 2。
  • 第三步,遇到 setTimeout 塞入宏任务队列,等待执行。
  • 第四步,执行 promise.then() ,注册成功和失败后的回调,由于promise的状态目前处于 pending 等待 状态,所以并不会执行传入的两个回调。
  • 第五步,执行 console.log(3) 输出结果 3。
  • 第六步,同步代码执行完毕,先检查微任务队列,微任务队列为空;再检查宏任务队列,宏任务队列存在需执行的任务,取第一个宏任务 setTimeout 执行,执行时调用 resolve ,修改Promise状态为 fulfilled 成功 , 并将 promise.then 注册的回调推入微任务队列,接着执行 console.log(4) 输出结果 4。
  • 第七步,同步代码执行完毕,再次执行上述事件循环,检查微任务队列,存在需要执行的微任务,按照 先进先出 的顺序执行所有的微任务,此时输出结果 fulfilled: 成功。
  • 执行完毕,输出结果:1、2、3、4、fulfilled: 成功。

回到正文,我们再用同样的代码执行手写 myPromise

class myPromise {
    // 省略
}

console.log(1);
const p1 = new myPromise((resolve, reject) => {
    console.log(2);
    setTimeout(() => {
        resolve('成功');
        console.log(4);
    });
})
p1.then(
    result => {
        console.log('fulfilled:', result);
    },
    reason => {
        console.log('rejected:', reason)
    }
)
console.log(3);

// 输出结果
1
2
3
4

可以发现 fulfilled: 成功 并没有输出,我们可以设想下会不会 then 方法没有执行,先回顾下 then 方法的逻辑:

class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
       onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
       onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
       
       if (this.PromiseState === myPromise.FULFILLED) {
           setTimeout(() => {
               onFulfilled(this.PromiseResult);
           })
       }

       if (this.PromiseState === myPromise.REJECTED) {
           setTimeout(() => {
               onRejected(this.PromiseResult);
           })
       }
    }
}

可以看出很可能判断条件没满足,也就是状态不对。那么我们可以先在三个位置打印出状态,再判断问题在哪:

class myPromise {
    // 省略
}

console.log(1);
const p1 = new myPromise((resolve, reject) => {
    console.log(2);
    setTimeout(() => {
        console.log('A', p1.PromiseState);
        resolve('成功');
        console.log('B', p1.PromiseState);
        console.log(4);
    });
})
p1.then(
    result => {
        console.log('C', p1.PromiseState);
        console.log('fulfilled:', result);
    },
    reason => {
        console.log('rejected:', reason)
    }
)
console.log(3);

// 输出结果
1
2
3
A pending
B fulfilled
4

结合上述执行顺序,我们先查看 第四步,执行 p1.then 时,此时 myPromise 状态为 pending ,所以并不会执行判断里的逻辑,该段逻辑执行完毕,这里和原生 Promise 有一点区别,并没有注册回调。

接着再查看 第六步,执行 setTimeout 宏任务时,myPromise 状态还没被修改,初始状态为 pending,所以此时执行 console.log('A', p1.PromiseState) 输出 A pending ; 接着执行 resolve('成功') 修改 myPromise 状态为 fulfilled ;再执行 console.log('B', p1.PromiseState),此时状态已修改,输出 B fulfilled;再执行 console.log(4) ,输出 4。这里和原生 Promise 区别在于执行 resolve 方法时,并没把 then 注册的回调推入微任务队列。

为了保持和原生 Promise 保持一致,我们需要在执行完 resolve 后再执行 then 中的回调。所以我们需要先创建 数组 ,来保存回调函数

为什么用 数组 来保存这些回调呢?因为一个 promise 实例可能会多次 then,也就是经典的 链式调用,而且数组是先进先出的顺序。

接着我们对手写 myPromise 改造,在实例化时,创建 onFulfilledCallbacksonRejectedCallbacks 两个数组,用来保存成功和失败的回调;并完善 then 方法中的代码,判断状态为 pending 时,暂时保存这两个回调;在执行 resolvereject 时,遍历执行自身的 callbacks 数组,即 then 方法注册的回调函数,并逐个执行:

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    constructor(executor) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        this.onFulfilledCallbacks = []; // 保存成功回调
        this.onRejectedCallbacks = []; // 保存失败回调
        try {
            executor(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
            // 遍历注册成功后的回调 并逐个执行
            this.onFulfilledCallbacks.forEach(callback => callback())
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
            // 遍历注册失败后的回调 并逐个执行
            this.onRejectedCallbacks.forEach(callback => callback())
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
        // 判断状态为 pending 时  保存 成功 和 失败 的回调
        if (this.PromiseState === myPromise.PENDING) {
           this.onFulfilledCallbacks.push(onFulfilled);
           this.onRejectedCallbacks.push(onRejected);
        }
        if (this.PromiseState === myPromise.FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.PromiseResult);
            });
        }
        if (this.PromiseState === myPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.PromiseResult);
            });
        }
    }
}

我们再来测试看下:

class myPromise {
    // 省略
}

console.log(1);
const p1 = new myPromise((resolve, reject) => {
    console.log(2);
    setTimeout(() => {
        console.log('A', p1.PromiseState);
        resolve('成功');
        console.log('B', p1.PromiseState);
        console.log(4);
    });
})
p1.then(
    result => {
        console.log('C', p1.PromiseState);
        console.log('fulfilled:', result);
    },
    reason => {
        console.log('rejected:', reason)
    }
)
console.log(3);

// 输出结果
1
2
3
A pending
C fulfilled
fulfilled: 成功
B fulfilled
4

虽然回调中的结果 fulfilled:成功 打印出来了,但是我们还发现,代码输出顺序还是不太对,原生 Promise 中,fulfilled: 成功 是最后输出的。

这里有一个很多人忽略的小细节,要确保 onFulfilledonRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。因此,在保存成功和失败回调时也要添加 setTimeout

class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
        // 判断状态为 pending 时  保存 成功 和 失败 的回调
        if (this.PromiseState === myPromise.PENDING) {
           // 注册回调 添加 setTimeout 确保执行顺序
           this.onFulfilledCallbacks.push(() => {
               setTimeout(() => {
                   onFulfilled(this.PromiseResult);
               })
           });
           this.onRejectedCallbacks.push(() => {
               setTimeout(() => {
                   onRejected(this.PromiseResult);
               })
           });
        }
        if (this.PromiseState === myPromise.FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.PromiseResult);
            });
        }
        if (this.PromiseState === myPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.PromiseResult);
            });
        }
    }
}

我们再来测下:

class myPromise {
    // 省略
}

console.log(1);
const p1 = new myPromise((resolve, reject) => {
    console.log(2);
    setTimeout(() => {
        console.log('A', p1.PromiseState);
        resolve('成功');
        console.log('B', p1.PromiseState);
        console.log(4);
    });
})
p1.then(
    result => {
        console.log('C', p1.PromiseState);
        console.log('fulfilled:', result);
    },
    reason => {
        console.log('rejected:', reason)
    }
)
console.log(3);

// 输出结果
1
2
3
A pending
B fulfilled
4
C fulfilled
fulfilled: 成功

当前实现完整代码:

class myPromise {
    static PENDING = 'pending';
    static FULFILLED = 'fulfilled';
    static REJECTED = 'rejected';
    constructor(executor) {
        this.PromiseState = myPromise.PENDING;
        this.PromiseResult = null;
        this.onFulfilledCallbacks = [];
        this.onRejectedCallbacks = [];
        try {
            executor(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
            this.reject(error)
        }
    }
    resolve(result) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
            this.onFulfilledCallbacks.forEach(callback => {
                callback(result)
            })
        }
    }
    reject(reason) {
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
            this.onRejectedCallbacks.forEach(callback => {
                callback(reason)
            })
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

        if (this.PromiseState === myPromise.PENDING) {
           this.onFulfilledCallbacks.push(() => {
               setTimeout(() => {
                   onFulfilled(this.PromiseResult);
               })
           });
           this.onRejectedCallbacks.push(() => {
               setTimeout(() => {
                   onRejected(this.PromiseResult);
               })
           });
        }
        if (this.PromiseState === myPromise.FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.PromiseResult);
            });
        }
        if (this.PromiseState === myPromise.REJECTED) {
            setTimeout(() => {
                onRejected(this.PromiseResult);
            });
        }
    }
}

再来验证下 Promise 的 then 方法的多次调用:

const promise = new myPromise((resolve, reject) => {
    setTimeout(() => {
        resolve('success')
    }, 1000);
})
promise.then(value => {
    console.log(1)
    console.log('resolve', value)
})
promise.then(value => {
    console.log(2)
    console.log('resolve', value)
})
promise.then(value => {
    console.log(3)
    console.log('resolve', value)
})

// 输出结果
1
resolve success
2
resolve success
3
resolve success

这说明我们当前的代码,已经可以实现 then 方法的多次调用。

实现 then 方法链式调用

我们先来看下原生 Promisethen 方法是如何链式调用的:

const p1 = new Promise((resolve, reject) => {
    resolve(10)
})
p1.then(res => {
    console.log('fulfilled', res)
    return 2 * res
}).then(res => {
    console.log('fulfilled', res)
})

// 输出结果
fulfilled 10
fulfilled 20

再举个例子:

const p2 = new Promise((resolve, reject) => {
    resolve(10)
})

p2.then(res => {
    console.log('fulfilled', res)
    return new Promise((resolve, reject) => resolve(3 * res))
}).then(res => {
    console.log('fulfilled', res)
})

// 输出结果
fulfilled 10
fulfilled 30

我们先尝试执行下当前 myPromise 是否可执行链式调用:

class myPromise {
    // 省略
}

// 测试代码
const p1 = new myPromise((resolve, reject) => {
    resolve(10)
})
p1.then(res => {
    console.log('fulfilled', res)
    return 2 * res
}).then(res => {
    console.log('fulfilled', res)
}) 

// 输出结果
Uncaught TypeError: Cannot read properties of undefined

毫无疑问结果报错了,提示 then 方法未定义。而原生 Promise.prototype.then() 方法返回一个新的Promise实例(注意,不是原来那个 Promise 实例),因此可以采用链式写法,即 then 方法后面再调用另一个 then 方法。

Promises/A+ 规范的理解

在改造之前,我们先要对 Promises/A+ 规范的理解。想要实现 then 方法的链式调用,就必须彻底搞懂 then 方法,我们先看下规范 2.2 中是怎么描述的:

ES6系列之 模拟实现Promise

规范在 2.2.7 这样描述,译文如下:

  • 2.2.7 then 方法必须返回一个 promise 对象 promise2 = promise1.then(onFulfilled, onRejected);
    • 2.2.7.1 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
    • 2.2.7.2 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回据因 e
    • 2.2.7.3 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
    • 2.2.7.4 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因

理解上面的 返回 部分非常重要,即:不论 promise1 被 reject 还是被 resolve 时,promise2 都会执行 Promise 解决过程:[[Resolve]](promise2, x),只有出现异常时才会被 rejected 。

注意 2.2.7.1 规范:

If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [[Resolve]](promise2, x).

即:如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)

规范在 2.3 中详细描述 Promise 解决过程 The Promise Resolution Procedure

ES6系列之 模拟实现Promise

译文如下:

◾ 2.3 Promise 解决过程

Promise 解决过程 是一个抽象的操作,其需输入一个 promise 和一个值,我们表示为 [[Resolve]](promise, x),如果 x 有 then 方法且看上去像一个 Promise ,解决程序即尝试使 promise 接受 x 的状态;否则其用 x 的值来执行 promise 。

这种 thenable 的特性使得 Promise 的实现更具有通用性:只要其暴露出一个遵循 Promises/A+ 协议的 then 方法即可;这同时也使遵循 Promises/A+ 规范的实现可以与那些不太规范但可用的实现能良好共存。

运行 [[Resolve]](promise, x) 需遵循以下步骤:

▪ 2.3.1 x 与 promise 相等

如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise

▪ 2.3.2 x 为 Promise

如果 x 为 Promise ,则使 promise 接受 x 的状态

  • 2.3.2.1 如果 x 处于等待状态, promise 需保持为等待状态直至 x 被执行或拒绝
  • 2.3.2.2 如果 x 处于成功状态,用相同的值执行 promise
  • 2.3.2.3 如果 x 处于拒绝状态,用相同的据因拒绝 promise

▪ 2.3.3 x 为对象或函数

如果 x 为对象或者函数:

  • 2.3.3.1 把 x.then 赋值给 then
  • 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
  • 2.3.3.3 如果 then 是函数,将 x 作为函数的作用域 this 调用之。传递两个回调函数作为参数,第一个参数叫做 resolvePromise ,第二个参数叫做 rejectPromise:
    • 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
    • 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
    • 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
    • 2.3.3.3.4 如果调用 then 方法抛出了异常 e
      • 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
      • 2.3.3.3.4.2 否则以 e 为据因拒绝 promise
    • 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise

▪ 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise

如果一个 promise 被一个循环的 thenable 链中的对象解决,而 [[Resolve]](promise, thenable) 的递归性质又使得其被再次调用,根据上述的算法将会陷入无限递归之中。算法虽不强制要求,但也鼓励施者检测这样的递归是否存在,若检测到存在则以一个可识别的 TypeError 为据因来拒绝 promise

Promises/A+ 规范的总结

基于规范的描述,我们得到以下几点:

  • then 方法本身会返回一个新的 Promise 对象,返回一个新的 Promise 以后它就有自己的 then 方法,这样就能实现无限的链式
  • 不论 promise1 被 resolve() 还是被 reject() 时, promise2 都会执行 Promise 解决过程:[[Resolve]](promise2, x)

在手写这里我们把这个 Promise 解决过程:[[Resolve]](promise2, x)  命名为 resolvePromise() 方法,参数为 (promise2, x, resolve, reject) 即:

function resolvePromise(promise2, x, resolve, reject) {}

resolvePromise()各参数的意义:

/**
 * 针对resolve()和reject()中不同值情况 进行处理
 * @param  {promise} promise2 promise1.then方法返回的新的 promise 对象
 * @param  {[type]} x         promise1 中 onFulfilled 或 onRejected 的返回值
 * @param  {[type]} resolve   promise2 的 resolve 方法
 * @param  {[type]} reject    promise2 的 reject 方法
 */
function resolvePromise(promise2, x, resolve, reject) {}

其实,这个resolvePromise(promise2, x, resolve, reject) 即 Promise 解决过程:[[Resolve]](promise2, x) 就是针对 resolve()reject() 中不同值情况进行处理。

resolve()reject() 返回的 x 值的几种情况:

  1. 普通值
  2. Promise 对象
  3. thenable 对象/函数

下面我们就根据总结的两点,结合 Promises/A+ 规范 来实现 then 方法的链式调用

Promises/A+ 规范改造过程

  • 2.2.7规范 then 方法必须返回一个 promise 对象:
class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
        
        // 返回一个 promise 对象
        const promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                setTimeout(() => {
                    onFulfilled(this.PromiseResult);
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    onRejected(this.PromiseResult);
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        onFulfilled(this.PromiseResult);
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        onRejected(this.PromiseResult);
                    });
                });
            }
        })
        
       return promise2
    }
}
  • 2.2.7.1规范 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x)
class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
        // 返回一个 promise 对象
        const promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                setTimeout(() => {
                    // onFulfilled 返回一个值 X 执行 resolvePromise 解决过程
                    let x = onFulfilled(this.PromiseResult);
                    resolvePromise(promise2, x, resolve, reject);
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    // onRejected 返回一个值 X 执行 resolvePromise 解决过程
                    let x = onRejected(this.PromiseResult);
                    resolvePromise(promise2, x, resolve, reject);
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        onFulfilled(this.PromiseResult);
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        onRejected(this.PromiseResult);
                    });
                });
            }
        })

        return promise2
    }
}

/**
 * Promise 解决过程
 * 针对 resolve() 和 reject() 中不同值情况 进行处理
 * @param  {promise} promise2 promise1.then 方法返回的新的 promise 对象
 * @param  {[type]} x         promise1 中 onFulfilled 或 onRejected 的返回值
 * @param  {[type]} resolve   promise2 的 resolve 方法
 * @param  {[type]} reject    promise2 的 reject 方法
 */
function resolvePromise(promise2, x, resolve, reject) {}
  • 2.2.7.2规范 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
        // 返回一个 promise 对象
        const promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                setTimeout(() => {
                    try {
                        // onFulfilled 返回一个值 X 执行 resolvePromise 解决过程
                        let x = onFulfilled(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e); // 捕获前面 onFulfilled 中抛出的异常
                    }
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    try {
                        // onRejected 返回一个值 X 执行 resolvePromise 解决过程
                        let x = onRejected(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e); // 捕获前面 onRejected 中抛出的异常
                    }
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        onFulfilled(this.PromiseResult);
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        onRejected(this.PromiseResult);
                    });
                });
            }
        })

        return promise2
    }
}

function resolvePromise(promise2, x, resolve, reject) {}

另外,结合 2.2.7.1 和 2.2.7.2 规范,我们还需处理 pending 状态的逻辑

class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

        const promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                // 根据 2.2.7.1 和 2.2.7.2 规范 处理
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
            }
        })

        return promise2
    }
}

function resolvePromise(promise2, x, resolve, reject) {}
  • 2.2.7.3规范 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
// 在 fulfilled 和 pending 状态中 添加 判断逻辑
class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

        const promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                setTimeout(() => {
                    try {
                        // onFulfilled 不是 函数
                        if(typeof onFulfilled !== 'function') {
                            resolve(this.PromiseResult);
                        }else {
                            let x = onFulfilled(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch(e) {
                        reject(e);
                    }
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.PromiseResult);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch(e) {
                        reject(e);
                    }
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            // onFulfilled 不是 函数
                            if(typeof onFulfilled !== 'function') {
                                resolve(this.PromiseResult);
                            }else {
                                let x = onFulfilled(this.PromiseResult);
                                resolvePromise(promise2, x, resolve, reject);
                            }
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
            }
        })

        return promise2
    }
}

function resolvePromise(promise2, x, resolve, reject) {}
  • 2.2.7.4规范 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
// 在 rejected 和 pending 状态中 添加 判断逻辑
class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };

        const promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                setTimeout(() => {
                    try {
                        if(typeof onFulfilled !== 'function') {
                            resolve(this.PromiseResult);
                        }else {
                            let x = onFulfilled(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch(e) {
                        reject(e);
                    }
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    try {
                        // onRejected 不是 函数
                        if(typeof onRejected !== 'function') {
                            reject(this.PromiseResult);
                        } else {
                            let x = onRejected(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch(e) {
                        reject(e);
                    }
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            if(typeof onFulfilled !== 'function') {
                                resolve(this.PromiseResult);
                            }else {
                                let x = onFulfilled(this.PromiseResult);
                                resolvePromise(promise2, x, resolve, reject);
                            }
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            // onRejected 不是 函数
                            if(typeof onRejected !== 'function') {
                                reject(this.PromiseResult);
                            } else {
                                let x = onRejected(this.PromiseResult);
                                resolvePromise(promise2, x, resolve, reject);
                            }
                        } catch(e) {
                            reject(e);
                        }
                    });
                });
            }
        })

        return promise2
    }
}

function resolvePromise(promise2, x, resolve, reject) {}

根据 2.2.7.3 和 2.2.7.4 规范 对 onFulfilled 和 onRejected 不是函数的情况已经做了更详细的描述,根据描述我们对 onFulfilled 和 onRejected 引入了新的参数校验,所以之前的参数校验就可以删除:

class myPromise {
    // 省略
    then(onFulfilled, onRejected) {
        // 以下代码 删除   
     /* onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
        onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
        */
    }
}

function resolvePromise(promise2, x, resolve, reject) {}

实现 resolvePromise 方法

  • 2.3.1规范 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        // 抛出错误 catch 捕获
        throw new TypeError('Chaining cycle detected for promise');
        // 也可以直接 reject 错误 两种写法都可
        // return reject(new TypeError('Chaining cycle detected for promise'));
    }
}

在这里我们只需要抛出一个 TypeError 的异常即可,因为调用 resolvePromise 方法外层的 try...catch 会捕获这个异常,然后 以 TypeError 为据因拒绝执行 promise。

我们来看下下面的例子:

const promise = new Promise((resolve, reject) => {
  resolve(100)
})
const p1 = promise.then(value => {
  console.log(value)
  return p1
})

// 输出结果
100
Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>

可以看出原生 Promise 执行后,报类型错误。

  • 2.3.2规范 如果 x 为 Promise ,则使 promise 接受 x 的状态
class myPromise {}

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        throw new TypeError('Chaining cycle detected for promise');
    }
    
    if (x instanceof myPromise) {
        /**
         * 如果 x 为 Promise ,则使 promise2 接受 x 的状态
         * 也就是继续执行 x,如果执行的时候拿到一个 y,则继续解析 y
         * 比如 p1.then(res => { reuturn new myPromise((resolve, reject) => { resolve('测试') }) })
         * 此时 y 等于 测试
         */
        x.then(y => {
           resolvePromise(promise2, y, resolve, reject)
        }, reject);
    }
}
  • 2.3.3规范 如果 x 为对象或者函数

在判断x是对象或函数时,x 不能是 null,因为 typeof null 的值也为 object

if (x != null && ((typeof x === 'object' || (typeof x === 'function'))))
  • 2.3.4规范 如果 x 不为对象或者函数,以 x 为参数执行 promise
function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        throw new TypeError('Chaining cycle detected for promise');
    }

    if (x instanceof myPromise) {
        /**
         * 2.3.2 如果 x 为 Promise ,则使 promise2 接受 x 的状态
         *       也就是继续执行 x,如果执行的时候拿到一个 y,则继续解析 y
         * 比如 p1.then(res => { reuturn new myPromise((resolve, reject) => { resolve('测试') }) })
         * 此时 y 等于 测试
         */
        x.then(y => {
            resolvePromise(promise2, y, resolve, reject)
        }, reject);
    } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) {
        // 2.3.3 如果 x 为对象或函数
        try {
            // 2.3.3.1 把 x.then 赋值给 then
            var then = x.then;
        } catch (e) {
            // 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
            return reject(e);
        }

        /**
         * 2.3.3.3 
         * 如果 then 是函数,则将 x 作为函数的作用域 this 调用
         * 传递两个回调函数作为参数
         * 第一个参数 resolvePromise ,第二个参数 rejectPromise
         */
        if (typeof then === 'function') {
            /**
             * 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,
             * 或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
             */
            let called = false; // 避免多次调用
            try {
                then.call(
                    x,
                    // 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
                    y => {
                        if (called) return; // 避免多次调用
                        called = true;
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    // 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                    r => {
                        if (called) return; // 避免多次调用
                        called = true;
                        reject(r);
                    }
                )
            } catch (e) {
                /**
                 * 2.3.3.3.4 如果调用 then 方法抛出了异常 e
                 * 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略
                 */
                if (called) return;
                called = true;

                /**
                 * 2.3.3.3.4.2 否则以 e 为据因拒绝 promise
                 */
                reject(e);
            }
        } else {
            // 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise
            resolve(x);
        }
    } else {
        // 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise
        return resolve(x);
    }
}

完整的 Promises/A+ 实现

class myPromise {
    // 创建 promise 的三个状态
    static PENDING = 'pending'; // 待定
    static FULFILLED = 'fulfilled'; // 已成功
    static REJECTED = 'rejected'; // 已拒绝

    // 构造函数:通过 new 命令生成对象实例时,自动调用类的构造函数
    constructor(executor) { // 给类的构造方法 constructor 添加一个参数 executor
        this.PromiseState = myPromise.PENDING; // 指定 Promise 对象的状态属性 PromiseState,初始值为pending
        this.PromiseResult = null; // 指定 Promise 对象的结果 PromiseResult
        this.onFulfilledCallbacks = []; // 保存成功回调
        this.onRejectedCallbacks = []; // 保存失败回调
        try {
            /**
             * executor()传入 resolve 和 reject
             * 实例创建完 外部调用 resolve() 和reject(),会导致 this 指向丢失
             * 这里使用 bind 修改 this 指向
             * new 对象实例时,自动执行 executor()
             */
            executor(this.resolve.bind(this), this.reject.bind(this));
        } catch (error) {
            /** 
             * 生成实例时(执行 resolve 和 reject ),如果报错,
             * 就把错误信息传入给 reject() 方法,并且直接执行 reject() 方法
             */
            this.reject(error)
        }
    }

    resolve(result) { // result 为成功状态时接收的终值
        // 只能由 pending 状态 => fulfilled 状态 (避免调用多次 resolve reject)
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.FULFILLED;
            this.PromiseResult = result;
            /**
             * 在执行 resolve 或者 reject 时,遍历自身的 callbacks 数组,
             * onFulfilledCallbacks 由 then 方法执行时 注册的成功 回调方法
             */
            this.onFulfilledCallbacks.forEach(callback => callback())
        }
    }

    reject(reason) { // reason为拒绝态时接收的终值
        // 只能由pending 状态 => rejected 状态 (避免调用多次resolve reject)
        if (this.PromiseState === myPromise.PENDING) {
            this.PromiseState = myPromise.REJECTED;
            this.PromiseResult = reason;
            this.onRejectedCallbacks.forEach(callback => callback())
        }
    }

    /**
     * [注册fulfilled状态/rejected状态对应的回调函数] 
     * @param {function} onFulfilled  fulfilled 状态时 执行的函数
     * @param {function} onRejected  rejected 状态时 执行的函数 
     * @returns {function} newPromsie  返回一个新的 promise 对象
     */
    then(onFulfilled, onRejected) {
        // 2.2.7规范 then 方法必须返回一个 promise 对象
        let promise2 = new myPromise((resolve, reject) => {
            if (this.PromiseState === myPromise.FULFILLED) {
                /**
                 * 为什么这里要加定时器setTimeout?
                 * 2.2.4规范 onFulfilled 和 onRejected 只有在执行环境堆栈仅包含平台代码时才可被调用 注1
                 * 这里的平台代码指的是引擎、环境以及 promise 的实施代码。
                 * 实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。
                 * 这个事件队列可以采用“宏任务(macro-task)”机制,比如setTimeout 或者 setImmediate; 也可以采用“微任务(micro-task)”机制来实现, 比如 MutationObserver 或者process.nextTick。
                 */
                setTimeout(() => {
                    try {
                        if (typeof onFulfilled !== 'function') {
                            // 2.2.7.3规范 如果 onFulfilled 不是函数且 promise1 成功执行, promise2 必须成功执行并返回相同的值
                            resolve(this.PromiseResult);
                        } else {
                            // 2.2.7.1规范 如果 onFulfilled 或者 onRejected 返回一个值 x ,则运行下面的 Promise 解决过程:[[Resolve]](promise2, x),即运行 resolvePromise()
                            let x = onFulfilled(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch (e) {
                        // 2.2.7.2规范 如果 onFulfilled 或者 onRejected 抛出一个异常 e ,则 promise2 必须拒绝执行,并返回拒因 e
                        reject(e); // 捕获前面 onFulfilled 中抛出的异常
                    }
                });
            } else if (this.PromiseState === myPromise.REJECTED) {
                setTimeout(() => {
                    try {
                        if (typeof onRejected !== 'function') {
                            // 2.2.7.4规范 如果 onRejected 不是函数且 promise1 拒绝执行, promise2 必须拒绝执行并返回相同的据因
                            reject(this.PromiseResult);
                        } else {
                            let x = onRejected(this.PromiseResult);
                            resolvePromise(promise2, x, resolve, reject);
                        }
                    } catch (e) {
                        reject(e) // 捕获前面 onRejected 中抛出的异常
                    }
                });
            } else if (this.PromiseState === myPromise.PENDING) {
                // pending 状态保存的 onFulfilled() 和 onRejected() 回调也要符合 2.2.7.1,2.2.7.2,2.2.7.3 和 2.2.7.4 规范
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            if (typeof onFulfilled !== 'function') {
                                resolve(this.PromiseResult);
                            } else {
                                let x = onFulfilled(this.PromiseResult);
                                resolvePromise(promise2, x, resolve, reject);
                            }
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            if (typeof onRejected !== 'function') {
                                reject(this.PromiseResult);
                            } else {
                                let x = onRejected(this.PromiseResult);
                                resolvePromise(promise2, x, resolve, reject);
                            }
                        } catch (e) {
                            reject(e);
                        }
                    });
                });
            }
        })

        return promise2
    }
}

/**
 * 针对resolve()和reject()中不同值情况 进行处理
 * @param  {promise} promise2 promise1.then 方法返回的新的 promise 对象
 * @param  {[type]} x         promise1 中 onFulfilled 或 onRejected 的返回值
 * @param  {[type]} resolve   promise2的 resolve 方法
 * @param  {[type]} reject    promise2的 reject 方法
 */
function resolvePromise(promise2, x, resolve, reject) {
    // 2.3.1规范 如果 promise 和 x 指向同一对象,以 TypeError 为据因拒绝执行 promise
    if (x === promise2) {
        throw new TypeError('Chaining cycle detected for promise');
    }

    if (x instanceof myPromise) {
        /**
         * 2.3.2 如果 x 为 Promise ,则使 promise2 接受 x 的状态
         *       也就是继续执行x,如果执行的时候拿到一个y,还要继续解析y
         * 比如 p1.then(res => { reuturn new myPromise((resolve, reject) => { resolve('测试') }) }) 
         * 此时 y 等于 测试
         */
        x.then(y => {
            resolvePromise(promise2, y, resolve, reject)
        }, reject);
    } else if (x !== null && ((typeof x === 'object' || (typeof x === 'function')))) {
        // 2.3.3 如果 x 为对象或函数
        try {
            // 2.3.3.1 把 x.then 赋值给 then
            var then = x.then;
        } catch (e) {
            // 2.3.3.2 如果取 x.then 的值时抛出错误 e ,则以 e 为据因拒绝 promise
            return reject(e);
        }

        /**
         * 2.3.3.3 
         * 如果 then 是函数,将 x 作为函数的作用域 this 调用
         * 传递两个回调函数作为参数
         * 第一个参数 resolvePromise ,第二个参数 rejectPromise
         */
        if (typeof then === 'function') {
            // 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 均被调用,或者被同一参数调用了多次,则优先采用首次调用并忽略剩下的调用
            let called = false; // 避免多次调用
            try {
                then.call(
                    x,
                    // 2.3.3.3.1 如果 resolvePromise 以值 y 为参数被调用,则运行 [[Resolve]](promise, y)
                    y => {
                        if (called) return;
                        called = true;
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    // 2.3.3.3.2 如果 rejectPromise 以据因 r 为参数被调用,则以据因 r 拒绝 promise
                    r => {
                        if (called) return;
                        called = true;
                        reject(r);
                    }
                )
            } catch (e) {
                /**
                 * 2.3.3.3.4 如果调用 then 方法抛出了异常 e
                 * 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已经被调用,则忽略之
                 */
                if (called) return;
                called = true;

                // 2.3.3.3.4.2 否则以 e 为据因拒绝 promise
                reject(e);
            }
        } else {
            // 2.3.3.4 如果 then 不是函数,以 x 为参数执行 promise
            resolve(x);
        }
    } else {
        // 2.3.4 如果 x 不为对象或者函数,以 x 为参数执行 promise
        return resolve(x);
    }
}

我们再来测试下:

class myPromise {}

const p1 = new myPromise((resolve, reject) => {
    resolve(100)
})

p1.then(res => {
    return 200
}).then(res => {
    console.log(res)
})

// 输出结果 
200

p1.then(null).then(res => {
    console.log(res)
})

// 输出结果 
100

const p2 = p1.then(res => {
    return p2
})
p2.then(null, err => {
    console.log(err)
})

// 输出结果
TypeError: Chaining cycle detected for promise
    at resolvePromise (index.html:157:23)
    at index.html:89:37

p1.then(res => {
    return new myPromise((resolve, reject) => {
        resolve(300)
    })
}).then(res => {
    console.log(res)
})

// 输出结果
300

Promise 测试

如何证明我们写的 myPromise 符合 Promises/A+  规范呢?

我们可以使用 Promises/A+ 官方的测试工具 promises-aplus-tests 来对我们的 myPromise 进行测试

  • 安装 promises-aplus-tests
npm install promises-aplus-tests -D

安装完后,目录结构如下:

- test
    - node_modules
    - myPromise.js
    - package.json
  • 修改 myPromise.js 文件,使用 CommonJS 对外暴露 myPromise
class myPromise {}

function resolvePromise(promise2, x, resolve, reject) {}

module.exports = myPromise;
  • 实现静态方法 deferred

使用 promises-aplus-tests 这个工具测试,必须实现一个静态方法 deferred(),官方对这个方法的定义如下:

ES6系列之 模拟实现Promise

大致译文如下:

我们要给自己手写的 myPromise 上实现一个静态方法 deferred(),该方法要返回一个包含 { promise, resolve, reject } 的对象:

  • promise 是一个处于 pending 状态的 Promsie
  • resolve(value) 用 value 解决上面那个 promise
  • reject(reason) 用 reason 拒绝上面那个 promise

deferred 实现:

class myPromise {}

function resolvePromise(promise2, x, resolve, reject) { }

myPromise.deferred = function () {
    let result = {};
    result.promise = new myPromise((resolve, reject) => {
        result.resolve = resolve;
        result.reject = reject;
    });
    return result;
}

module.exports = myPromise;
  • 配置 package.json
// package.json
{
  "devDependencies": {
    "promises-aplus-tests": "^2.1.2"
  },
  "scripts": {
    "test": "promises-aplus-tests myPromise"
  }
}
  • 执行测试
npm run test

测试结果完美通过官方 872 个测试用例🎉🎉🎉

参考

Promises/A+

ECMAScript 6 Promise

MDN Promise

深入 Promise 原理

10分钟彻底掌握 Promise 及原理

从一道让我失眠的 Promise 面试题开始,深入分析 Promise 实现细节

BAT前端经典面试问题:史上最最最详细的手写Promise教程

手把手一行一行代码教你“手写Promise”,完美通过 Promises/A+ 官方872个测试用例