手写实现Promise

个人主页原文链接

自己实现一个promise是前端常见的面试题之一(虽然我还没遇到过),同时也是深入理解promise的一种途径,通过自己实现,来更加深刻的理解其中的原理

要自己实现promise,首先得了解Promises/A+条例到底有哪些内容~

  • promise表示异步操作的最终结果。
  • 与promise进行交互的主要方式是通过其then方法,该方法注册回调以接收promise的value或无法履行promise的reason。
  • 该规范详细说明了该then方法的行为。

一、实现一个同步版的promise

同步版先不考虑其他的,直接实现能按照 promise --> then 的顺序执行

  • 先按照规范,定几个标记量
const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"
  • promise.then同步执行
function Promise(fn){
    let that = this
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    
    let resolve = function(value){
        that.value = value
        that.status = FULFILLED
    }
    let reject = function(reason){
        that.reason = reason
        that.status = REJECTED
    }
    
    try{
        fn(resolve,reject)
    }catch(e){
        reject(e)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){
    if(this.status === FULFILLED){
        onFulfilled(this.value)
    }
    if(this.status === REJECTED){
        onRejected(this.reason)
    }
}

二、实现异步

接下来在同步版的基础上再解决两个问题

  1. 同步版本在执行异步代码时什么都没有返回
new Promise((resolve,reject)=>{
    setTimeout(()=>{resolve(1)})
}).then(x=>console.log(x))
  • 这是因为在执行then时,resolve还没有执行,status还是PENDING的状态
  1. 如果then中的参数不是Function时的应该有一个默认的回调函数

因此如果执行到then时,需要如下操作:

  • 先判断 onFulfilled与onRejected是否是函数
  • 然后判断status的状态,如果status还是PENDING的话可以先将then中的两个回调函数存起来,待到执行resolve或者reject时再执行
function Promise(fn){
    let that = this
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.resolvedCb = []
    this.rejectedCb = []
    
    let resolve = function(value){
        that.value = value
        that.status = FULFILLED
        that.resolvedCb.forEach(cb=>cb(that.value))
    }
    let reject = function(reason){
        that.reason = reason
        that.status = REJECTED
        that.rejectedCb.forEach(cb=>cb(that.reason))
    }
    
    try{
        fn(resolve,reject)
    }catch(e){
        reject(e)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){
    onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
    onRejected = onRejected instanceof Function?onRejected:()=>{}

    if(this.status === FULFILLED){
        onFulfilled(this.value)
    }
    if(this.status === REJECTED){
        onRejected(this.reason)
    }
    if(this.status === PENDING){
        this.resolvedCb.push(onFulfilled)
        this.rejectedCb.push(onRejected)
    }
}

其中的关键代码如下:

  • 如果状态是PENDING,先保存回调函数
    if(this.status === PENDING){
        this.resolvedCb.push(onFulfilled)
        this.rejectedCb.push(onRejected)
    }
  • 异步的情况下resolve或者reject会在then之后执行,因此执行时,回调函数已经放入Cb数组中,可以遍历执行Cb中的回调函数
 that.resolvedCb.forEach(cb=>cb(that.value))
 that.rejectedCb.forEach(cb=>cb(that.reason))
  • 如果onFulfilled与onRejected不是函数,应该使用一个默认的函数
onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
onRejected = onRejected instanceof Function?onRejected:()=>{}

三、then的链式调用

根据Promises/A+协议,then也应该返回一个promise,同时这也是then链式调用的条件

  • 先考虑情况如下
Promise.prototype.then = function(onFulfilled,onRejected){
    onFulfilled = onFulfilled instanceof Function?onFulfilled:()=>{}
    onRejected = onRejected instanceof Function?onRejected:()=>{}

    if(this.status === FULFILLED){
        onFulfilled(this.value)
        return new Promise(()=>{})
    }
    if(this.status === REJECTED){
        onRejected(this.reason)
        return new Promise(()=>{})
    }
    if(this.status === PENDING){
        this.resolvedCb.push(onFulfilled)
        this.rejectedCb.push(onRejected)
        return new Promise(()=>{})
    }
}

然后FULFILLED与REJECTED状态下,then返回的内容会直接成为下一个then的回调函数的输入

  • return的内容是下一个then的onFulfilled的输入,应该用resolve包裹
  • throw的内容是下一个then的onRejected的输入,应该用reject包裹

首先要能够捕获throw,然后根据结果类型判断使用resolve还是reject

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            try {
                let res = onFulfilled(this.value)
                resolve(res)
            } catch (e) {
                reject(e)
            }
        })
    }

这样就实现了同步版的then链式调用

function Promise(fn) {
    ……
}

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
    onRejected = onRejected instanceof Function ? onRejected : () => {}

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onFulfilled(this.value))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === REJECTED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onRejected(this.reason))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === PENDING) {
        this.resolvedCb.push(onFulfilled)
        this.rejectedCb.push(onRejected)
        return new Promise(() => {

        })
    }
}

接着考虑异步的情况下,异步的情况下,执行到then时状态还是PENDING,之后的then也是在PENDING状态下返回的promise的基础上调用的


考虑异步,没有链式调用时只需要把onFulfilled、onRejected放入回调数组中,链式调用的话,应该把FULFILLED、REJECTED状态的promise中的回调函数放入回调数组中(这里逻辑感觉挺复杂的,主要是保证执行resolvedCb或rejectedCb中内容执行时,有和FULFILLED或REJECTED中promise的回调执行相同的效果)

这里注意一点,onFulfilled和onRejected的输入是新promise的value或者reason属性,因此直接用this,但是整个回调是放在上一个promise的数组中的,因此用that标识原来的this

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
    onRejected = onRejected instanceof Function ? onRejected : () => {}
    
    let that = this
    ……
    if (this.status === PENDING) {
        return new Promise((resolve, reject) => {
            that.resolvedCb.push(() => {
                try {
                    resolve(onFulfilled(this.value))
                } catch (e) {
                    reject(e)
                }
            })
            that.rejectedCb.push(() => {
                try {
                    resolve(onRejected(this.reason))
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}

这一步的完整程序如下

const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"

function Promise(fn) {
    let that = this
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.resolvedCb = []
    this.rejectedCb = []

    let resolve = function (value) {
        that.value = value
        that.status = FULFILLED
        that.resolvedCb.forEach(cb => cb(that.value))
    }
    let reject = function (reason) {
        that.reason = reason
        that.status = REJECTED
        that.rejectedCb.forEach(cb => cb(that.reason))
    }

    try {
        fn(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
    onRejected = onRejected instanceof Function ? onRejected : () => {}

    let that = this

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onFulfilled(this.value))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === REJECTED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onRejected(this.reason))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === PENDING) {
        return new Promise((resolve, reject) => {
            that.resolvedCb.push(() => {
                try {
                    resolve(onFulfilled(this.value))
                } catch (e) {
                    reject(e)
                }
            })
            that.rejectedCb.push(() => {
                try {
                    resolve(onRejected(this.reason))
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}

这个版本基本上能和标准的promise一致了,但是还有一些细节问题,比如下面代码的执行结果

new Promise((resolve, reject) => {
    resolve(new Promise((rs, rj) => {rs(9)}))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//res:9
new Promise((resolve, reject) => {
    resolve(new Promise((rs, rj) => {rj(9)}))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//err:9

因此下一步就是解决resolve的内容是promise的情况

ps:下面代码效果是一致的,即只有resolve会解析内部promise的内容

new Promise((resolve, reject) => {
    reject(new Promise((rs, rj) => {rs(9)}))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//err:[object Promise]
new Promise((resolve, reject) => {
    reject(new Promise((rs, rj) => {rj(9)}))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//err:[object Promise]

同理then中return返回的promise也会解析,throw返回的promise不会解析

new Promise((resolve, reject) => {
    resolve(1)
}).then(res => {
    return new Promise(resolve=>resolve(res))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
//res:1

new Promise((resolve, reject) => {
    resolve(1)
}).then(res => {
    throw new Promise(resolve=>resolve(res))
}).then(res => {
    console.log(`res:${res}`)
},err => {
    console.log(`err:${err}`)
})
// err:[object Promise]

四、解决resolve的内容是promise的情况

只有resolve和return中的promise会被解析,即只用this.value会被解析,而this.reason则不会

因此只需要修改if (this.status === FULFILLED) {}that.resolvedCb.push(() => {})中的内容

先考虑同步状态,执行到this.status === FULFILLED时,如果this.value是一个promise的话,相当于之后then的链式调用都是接到这个promise后面的,因此:

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            if (this.value instanceof Promise) {
                this.value.then(onFulfilled, onRejected)
            } else {
                try {
                    resolve(onFulfilled(this.value))
                } catch (e) {
                    reject(e)
                }
            }
        })
    }

再考虑异步的,直接和上面一步同理

    if (this.status === PENDING) {
        return new Promise((resolve, reject) => {
            that.resolvedCb.push(() => {
                if (this.value instanceof Promise) {
                    this.value.then(onFulfilled, onRejected)
                } else {
                    try {
                        resolve(onFulfilled(this.value))
                    } catch (e) {
                        reject(e)
                    }
                }
            })
            that.rejectedCb.push(() => {
                try {
                    resolve(onRejected(this.reason))
                } catch (e) {
                    reject(e)
                }
            })
        })
    }

完整版如下:

const PENDING = "pending"
const FULFILLED = "fulfilled"
const REJECTED = "rejected"

function Promise(fn) {
    let that = this
    this.status = PENDING
    this.value = undefined
    this.reason = undefined
    this.resolvedCb = []
    this.rejectedCb = []

    let resolve = function (value) {
        that.value = value
        that.status = FULFILLED
        that.resolvedCb.forEach(cb => cb(that.value))
    }
    let reject = function (reason) {
        that.reason = reason
        that.status = REJECTED
        that.rejectedCb.forEach(cb => cb(that.reason))
    }

    try {
        fn(resolve, reject)
    } catch (e) {
        reject(e)
    }
}

Promise.prototype.then = function (onFulfilled, onRejected) {
    onFulfilled = onFulfilled instanceof Function ? onFulfilled : () => {}
    onRejected = onRejected instanceof Function ? onRejected : () => {}

    let that = this

    if (this.status === FULFILLED) {
        return new Promise((resolve, reject) => {
            if (this.value instanceof Promise) {
                this.value.then(onFulfilled, onRejected)
            } else {
                try {
                    resolve(onFulfilled(this.value))
                } catch (e) {
                    reject(e)
                }
            }
        })
    }
    if (this.status === REJECTED) {
        return new Promise((resolve, reject) => {
            try {
                resolve(onRejected(this.reason))
            } catch (e) {
                reject(e)
            }
        })
    }
    if (this.status === PENDING) {
        return new Promise((resolve, reject) => {
            that.resolvedCb.push(() => {
                if (this.value instanceof Promise) {
                    this.value.then(onFulfilled, onRejected)
                } else {
                    try {
                        resolve(onFulfilled(this.value))
                    } catch (e) {
                        reject(e)
                    }
                }
            })
            that.rejectedCb.push(() => {
                try {
                    resolve(onRejected(this.reason))
                } catch (e) {
                    reject(e)
                }
            })
        })
    }
}

效果对比:
手写实现Promise_第1张图片

五、promise的部分API实现

Promise.resolve

Promise.resolve = function(value){
    return new Promise(resolve=>{
        resolve(value)
    })
}

Promise.reject

Promise.reject = function(reason){
    return new Promise((resolve,reject)=>{
        reject(reason)
    })
}

Promise.all

Promise.all方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);

p的状态由p1、p2、p3决定,分成两种情况。

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。
  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。
Promise.all = function (promises) {
    let resolveList = []
    return new Promise((resolve, reject) => {
        if(promises.length === 0){ //promises为空数组的情况下,会返回resolve([])
            resolve(resolveList)
        }
        promises.forEach(p => {
            Promise.resolve(p).then(re => {
                resolveList.push(re)
                if (promises.length === resolveList.length) {
                    //因为promise异步的原因,还是得放里面
                    resolve(resolveList)
                }
            }, rj => {
                reject(rj)
            })
        })
    })
}

ps:有个bug,如下

var a = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('3')
    },300)
})
var b = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('2')
    },200)
})
var c = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('1')
    },100)
})
var d = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('4')
    },400)
})

Promise.all([a, b, c, d]).then(res => console.log(res), res => console.log(res))

真正的promise.all返回是[3,2,1,4],我的返回是[1,2,3,4]

Promise.race

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.race = function (promises) {
    let flag = true
    return new Promise((resolve, reject) => {
        promises.forEach(p => {
            Promise.resolve(p).then(re => {
                if (flag) {
                    flag = false
                    resolve(re);
                }
            }, rj => {
                if (flag) {
                    flag = false
                    reject(rj);
                }
            })
        })
    })
}

你可能感兴趣的:(手写实现Promise)