手写一个Promise

首先我们先去了解一下Promise/A+规范.

Promise 相关术语

  • 解决(fulfill): 指一个 promise 成功时进行的一系列操作,如状态的改变、回调的执行。虽然规范中用 fulfill 来表示解决,但在后来的 promise 实现多以 resolve 来指代之。
  • 拒绝(reject):指一个 promise 失败时进行的一系列操作。
  • 终值(eventual value):所谓终值,指的是 promise 被解决时传递给解决回调的值,由于 promise 有一次性的特征,因此当这个值被传递时,标志着 promise 等待状态的结束,故称之终值,有时也直接简称为值(value)。
  • 据因(reason):也就是拒绝原因,指在 promise 被拒绝时传递给拒绝回调的值。

Promise 的状态

一个 Promise 的当前状态必须为以下三种状态中的一种:等待状态(Pending)执行状态(Fulfilled)拒绝状态(Rejected)

等待状态(Pending)

处于等待状态时,promise 需满足以下条件:

  • 可以迁移至执行状态或拒绝状态

执行状态(Fulfilled)

处于执行状态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的终值

拒绝状态(Rejected)

处于拒绝状态时,promise 需满足以下条件:

  • 不能迁移至其他任何状态
  • 必须拥有一个不可变的据因

这里不可变指的是恒等(即可用 === 判断相等),而不是意味着更深层次的不可变。(当 value 或 reason 不是基本值时,只要求其引用地址相等,但属性值可被更改)。

Then 方法

一个 promise 必须提供一个 then 方法以访问其当前值,终值和据因。

promise 的 then 方法接受两个参数:

promise.then(onFulfilled, onRejected)

参数可选

onFulfilledonRejected 都是可选参数。

  • 如果 onFulfilled 不是函数,其必须被忽略
  • 如果 onRejected 不是函数,其必须被忽略

onFulfilled 特征

如果 onFulfilled 是函数:

  • promise 执行接受后其必须被条用,其第一个参数为 promise 的终值
  • promise 执行结束前其不可被调用
  • 其调用次数不可超过一次

onRejected 特征

如果 onRejected 是函数

  • promise 被拒绝执行后其必须被调用,其第一个参数为 promise 的据因
  • promise 被拒绝执行前其不可被调用
  • 其调动次数不可超过一次

更过规范请自行查看 Promise/A+ 规范

实现一个简易版 Promise

首先先搭建构建函数的大体框架

  • 首先创建三个常量用于表示状态
  • 在函数体内部创建常量 that,因为代码可能会异步执行,用于获取正确的this对象
  • 一开始 Promise 的状态应该为 pengding
  • value 变量用于保存 resolve 或者 reject 中传入的值
  • resolvedCallbacksrejectedCallbacks 用于保存 then 中的回调,因为当执行完Promise时状态可能还是等待中,这时候应该吧then中的回调保存起来用于状态改变时使用
const PENDING = 'pengding'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn) {
    const that = this
    this.state = PENDING
    this.value = null
    this.resolvedCallbacks = []
    this.rejectedCallbacks = []

    // 待完善 resolve 和 reject 函数
    // 待完善执行 fn 函数
}

接下来来完善resolverejected函数,添加在MyPromise函数内部

  • 首先两个函数都得判断当前状态是否为等待中,因为规范规定只有等待状态才可以改变状态
  • 将当前状态更改为对应状态,并且将传入的值赋值给value
  • 遍历回调数组并执行
function resolve(value){
    if(that.state === PENDING){
        that.state = RESOLVED
        that.value = value
        that.resolvedCallbacks.map((cb) => cb(that.value))
    }
}

function rejected(value){
    if(that.state === PENDING){
        that.state = REJECTED
        that.value = value
        that.rejectedCallbacks.map((cb) => cb(that.value))
    }
}

完成以上两个函数以后,就应该实现如何执行Promise中传入的函数了

  • 执行传入的参数并且将之前两个函数当做参数传进去
  • 要注意的是,可能执行函数过程中会遇到错误,需要捕获错误并且执行reject函数
try{
    fn(resolve,reject)
} catch(e){
    reject(e)
}

最后来实现then函数

  • 首先判断两个参数是否为函数类型,因为两个参数是可选参数
  • 当参数不是函数类型时,需要创建一个函数赋值给对应的参数,同时也实现了透传,比如以下代码
    // 该代码目前在简单版中会报错
    // 只是作为一个透传的例子
    Promise.resolve(4).then().then((value) => console.log(value))
    
  • 接下来就是一系列判断状态逻辑,当状态不是等待状态时,就是执行相对应的函数。如果状态是等待状态的话,就往回调函数中push函数。
MyPromise.prototype.then = function (onFulfilled,onRejected) {
    const that = this
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r}

    if (that.state === PENDING) {
        that.resolvedCallbacks.push(onFulfilled)
        that.rejectedCallbacks.push(onRejected)
    }

    if (that.state === RESOLVED) {
        onFulfilled(that.value)
    }

    if (that.state === REJECTED) {
        onRejected(that.value)
    }
}

以上就是简单版promise实现,接下来继续实现完整版Promise

事件一个符合 Promise/A+ 规范的 Promise

首先先改造一下 resolvereject 函数

  • 对于resolve函数来说,首先需要判断传入的值是否为Promise类型
  • 为了保证函数执行顺序,需要将两个函数体代码使用setTimeout包裹起来
function resolve(value){
    if (value instanceof MyPromise) {
        return value.then(resolve,reject)
    }
    setTimeout(() =>{
        if(that.state === PENDING){
            that.state = RESOLVED
            that.value = value
            that.resolvedCallbacks.map((cb) => cb(that.value))
        }
    },0)
}

function reject(value){
    setTimeout(() =>{
        if(that.state === PENDING){
            that.state = REJECTED
            that.value = value
            that.rejectedCallbacks.map((cb) => cb(that.value))
        }
    },0)
}

接下来继续改造then函数中的代码,首先我们需要新增一个变量promise2,因为每个then函数都需要返回一个新的Promise对象,该变量用于保存新的返回对象,我们先改造判断等待状态的逻辑

  • 首先我们返回一个新的Promise对象,并在Promise中传入一个函数
  • 函数的基本逻辑和之前一样,往回调数组中push函数
  • 同样在执行函数的过程中可能会遇到错误,所以使用了try...catch包裹
  • 规范规定,执行onFulfilled或者onRejected函数时会返回一个x,并且执行Promise解决过程,这是为了不同的Promise都可以兼容使用,比如 JQuery 的Promise能兼容ES6的Promise
  • 接下来改造判断执行状态的逻辑
  • 这段代码和判断等待状态的逻辑基本一样,无非是传入的函数的函数体需要异步执行,这也是规范规定的
MyPromise.prototype.then = function (onFulfilled,onRejected) {
    const that = this
    let promise2
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : r => {throw r}

    // resolutionProcedure 函数待实现

    if (that.state === PENDING) {
        return (promise2 = new MyPromise((resolve,reject) => {
            that.resolvedCallbacks.push(() => {
                try {
                    const x = onFulfilled(that.value)
                    resolutionProcedure(promise2,x,resolve,reject)
                } catch (e) {
                    reject(e)
                }
            })


            that.rejectedCallbacks.push(() => {
                try {
                    const x = onRejected(that.value)
                    resolutionProcedure(promise2,x,resolve,reject)
                } catch (e) {
                    reject(e)
                }
            })
        }))
    }

    if (that.state === RESOLVED) {
        return (promise2 = new MyPromise((resolve,reject) => {
            setTimeout(() => {
                try {
                    const x = onFulfilled(that.value)
                    resolutionProcedure(promise2,x,resolve,reject)
                } catch (e) {
                    reject(e)
                }
            })
        }))
    }

    if (that.state === REJECTED) {
        return (promise2 = new MyPromise((resolve,reject) => {
            setTimeout(() => {
                try {
                    const x = onRejected(that.value)
                    resolutionProcedure(promise2,x,resolve,reject)
                } catch (e) {
                    reject(e)
                }
            })
        }))
    }
}

最后,就是实现兼容各种PromiseresolutionProcedure 函数

  • 首先规范规定了x不能与promise2相等,这样会发生循环引用的问题
  • 然后需要判断x的类型
  • 如果xPromise的话需要判断一下几个情况:
  • 1.如果x处于等待状态,Promise需保持为等待状态直至x被执行或拒绝
  • 2.如果x处于其他状态,则用相同的值处理Promise
  • 当然以上这些是规范需要我们判断的情况,实际上我们不判断状态也是可行的。
  • 接下来完成剩余代码
  • 首先创建一个变量called用于判断是否已经调用过函数
  • 然后判断x是否为对象或者函数,如果都不是的话,将x传入resolve
  • 如果x是对象或者函数的话,先把x.then赋值给then,然后判断then的类型,如果不是函数类型的话,就将x传入resolve
  • 如果then是函数类型的话,就将x作为函数的作用域this调用之,并且传递两个回调函数作为参数,第一参数叫做resolvePromise,第二个参数叫做rejectPromise,两个回调函数都需要判断是否已经执行过函数,然后进行相应的逻辑
  • 以上代码在执行过程中如果抛出错误,错误将传入reject函数中
function resolutionProcedure(promise2,x,resolve,reject){
    if (promise2 === x) {
        return reject(new TypeError('Error'))
    }

    if (x instanceof MyPromise) {
        x.then(function (value) {
            resolutionProcedure(promise2,value,resolve,reject)
        })
    }

    let called = false

    if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
        try {
            let then = x.then
            if (typeof then === 'function') {
                then.call(x,(y) => {
                    if (called) return 
                    called = true
                    resolutionProcedure(promise2,y,resolve, reject)
                },
                (e) => {
                    if (called) return 
                    called = true
                    reject(e)
                }
                )
            }
            else {
                resolve(x)
            }

        } catch (e) {
            if (called) return
                called = true
                reject(e)
            }
    }
    else {
        resolve(x)
    }
}

以上就是符合 Promise/A+ 规范的实现了

扩展阅读