【大厂面试必考】从使用层面来手写promise源码

216 阅读17分钟

promise介绍和使用

这里不多说,直接看阮一峰老师的ES6入门书籍

promise类的实现

言归正传,此文全部都是以使用的层面来思考原理手写promise,会穿插一些promise使用的规则,可能会有点精简,具体使用规则请回到阮一峰老师的书籍。

promise类的使用

  1. Promise是一个类,在使用new关键字实例化的时候会传递一个执行器进去,执行器是同步代码会立即执行。
  2. Promise一共有三种状态,分别为成功(fulfilled)、失败(rejected)、和等待(pending),只可能是等待变为其他两种状态中的任意一种,一旦改变就定型,无法再次改变。
  3. resolve和rejecte用来更改promise状态。
  4. then方法判断状态,如果状态成功,调用成功回调函数,如果状态是失败则调用失败函数,then方法是被定义在原型对象中的。
  5. resolve方法会传入一个参数,这个参数用来修改成功或者失败后的原因,后期这个原因将会在then方法注册的回调函数中作为参数使用

正常使用promise的时候,我们是这样使用的:

new Promise((resolve,reject)=>{
    resolve('ok')
    // reject('fail')
})

我们会往promise构造函数塞一个函数进去作为执行器,执行器函数在promise实例化的时候马上被构造函数执行,这个函数有两个参数,一个resolve,一个reject,这两个参数都是方法,可以改变promise的状态,且promise有状态,所以还可以声明一个status作为它的状态。 以下,我们自己实现的promise类统一声明为MyPromise

//MyPromise.js

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


class MyPromise{
    constructor(executor){
        executor(this.resolve,this.reject)
    }
    //初始化为准备状态
    status = PENDING
    //成功的原因
    value = undefined
    //失败的原因
    reason = undefined
    resolve = (value)=>{
        //状态一旦改变就定型
        if(this.status != PENDING)  return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
    }
    reject = (reason)=>{
        if(this.status != PENDING)  return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
    }
    then(successCallback,failCallback){
        if(this.status == FULFILLED){
            //成功则调用成功以后的回调
            successCallback(this.value)
        }else if(this.status == REJECTED){
            //失败则调用失败后的回调
            failCallback(this.reason)
        }
    }
}


//CommonJS模块方案导出
module.exports = MyPromise
//index.js

const MyPromise = require('./MyPromise')

new MyPromise((resolve,reject)=>{
    resolve('ok')
}).then(res=>{
    console.log(res)
})

用nodemon运行一下index.js

image.png 此时可以看到这个promise已经初具雏形了

Promise的异步执行

在刚才这个版本的promise,我们只是实现了promise类,但是我们常用的promise一般是在异步环境中执行,比如我们刚才这个版本的promise是无法在setTimeout中改变状态的

image.png 此时控制台并没有输出任何东西,表示then函数的成功回调并没有被执行,那么我们现在来实现一个支持异步模式的promise。
首先,思考一下,为什么不能异步执行。因为当你调用.then方法的时候,此时promise还是pending状态,两秒以后才是resolve状态。那么此时.then方法里面两个if都没有匹配到,所以此时我们要做的应该是在.then函数里面增加一个pending状态的判断,如果执行.then函数的时候promise是pending状态,此时就把成功回调和失败回调都储存起来,等resolve的时候再调用。这也是为什么很多地方使用.then的时候说是**“注册回调函数”** 的原因。

//MyPromise.js

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


class MyPromise{
    constructor(executor){
        executor(this.resolve,this.reject)
    }
    //初始化为准备状态
    status = PENDING
    //成功的原因
    value = undefined
    //失败的原因
    reason = undefined
    //存储起来的回调
    successCallback = undefined
    failCallback = undefined
    resolve = (value)=>{
        //状态一旦改变就定型
        if(this.status != PENDING)  return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
        //如果之前存储了回调函数则此时调用
        this.successCallback && this.successCallback(this.value)
    }
    reject = (reason)=>{
        if(this.status != PENDING)  return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
        //如果之前存储了回调函数则此时调用
        this.failCallback && this.failCallback(this.reason)
    }
    then(successCallback,failCallback){
        if(this.status == FULFILLED){
            //成功则调用成功以后的回调
            successCallback(this.value)
        }else if(this.status == REJECTED){
            //失败则调用失败后的回调
            failCallback(this.reason)
        }else{
            //pending状态 存储回调函数
            this.successCallback = successCallback
            this.failCallback = failCallback
        }
    }
}


//CommonJS模块方案导出
module.exports = MyPromise

image.png

可以看到此时是两秒后执行的打印,这里index.js代码就不贴了。

实现promise的.then方法的多次调用

我们此时的promise是可以多次调用.then方法的

image.png 但是在promise中异步改变状态是不行的

image.png 所以,我们要在注册回调的时候,存到数组里面去,在resolve或者reject中再把数组中的回调函数从第一个开始一个个执行


//MyPromise.js
const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'


class MyPromise{
    constructor(executor){
        executor(this.resolve,this.reject)
    }
    //初始化为准备状态
    status = PENDING
    //成功的原因
    value = undefined
    //失败的原因
    reason = undefined
    //存储起来的回调
    successCallback = []
    failCallback = []
    resolve = (value)=>{
        //状态一旦改变就定型
        if(this.status != PENDING)  return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
        //如果之前存储了回调函数则此时调用
        while(this.successCallback.length){
            this.successCallback.shift()(this.value)
        }
    }
    reject = (reason)=>{
        if(this.status != PENDING)  return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
        //如果之前存储了回调函数则此时调用
        while(this.failCallback.length){
            this.failCallback.shift()(this.reason)
        }
    }
    then(successCallback,failCallback){
        if(this.status == FULFILLED){
            //成功则调用成功以后的回调
            successCallback(this.value)
        }else if(this.status == REJECTED){
            //失败则调用失败后的回调
            failCallback(this.reason)
        }else{
            //pending状态 存储回调函数
            this.successCallback.push(successCallback) 
            this.failCallback.push(failCallback)
        }
    }
}


//CommonJS模块方案导出
module.exports = MyPromise

image.png

promise的链式调用

promise是可以被链式调用的,then方法返回一个promise,这个promise的resolve的值就是上一个.then函数的返回值。 要实现这一点,首先我们得在.then函数中new一个promise,在执行回调函数的时候,把回调函数的返回值设为这个new出来的promise的resolve中,最后返回这个promise。

//MyPromise.js

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


class MyPromise{
    constructor(executor){
        executor(this.resolve,this.reject)
    }
    //初始化为准备状态
    status = PENDING
    //成功的原因
    value = undefined
    //失败的原因
    reason = undefined
    //存储起来的回调
    successCallback = []
    failCallback = []
    resolve = (value)=>{
        //状态一旦改变就定型
        if(this.status != PENDING)  return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
        //如果之前存储了回调函数则此时调用
        while(this.successCallback.length){
            this.successCallback.shift()(this.value)
        }
    }
    reject = (reason)=>{
        if(this.status != PENDING)  return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
        //如果之前存储了回调函数则此时调用
        while(this.failCallback.length){
            this.failCallback.shift()(this.reason)
        }
    }
    then(successCallback,failCallback){
        let promise2 = new MyPromise((resolve,reject)=>{
            if(this.status == FULFILLED){
                //成功则调用成功以后的回调
                let x = successCallback(this.value)
                resolve(x)
            }else if(this.status == REJECTED){
                //失败则调用失败后的回调
                let x = failCallback(this.reason)
                reject(x)
            }else{
                //pending状态 存储回调函数
                this.successCallback.push(successCallback) 
                this.failCallback.push(failCallback)
            }
        })
        return promise2
    }
}


//CommonJS模块方案导出
module.exports = MyPromise

image.png

此时已经链式调用成功,但是你会发现异步的情况链式调用并没有生效,别急,一步步来。

promise链式调用返回promise对象

如果promise的回调函数返回的不是普通值,很明显这里不能再用

let x = successCallback(this.value)
resolve(x)

来简单解决,需要分类讨论

//MyPromise.js

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


class MyPromise{
    constructor(executor){
        executor(this.resolve,this.reject)
    }
    //初始化为准备状态
    status = PENDING
    //成功的原因
    value = undefined
    //失败的原因
    reason = undefined
    //存储起来的回调
    successCallback = []
    failCallback = []
    resolve = (value)=>{
        //状态一旦改变就定型
        if(this.status != PENDING)  return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
        //如果之前存储了回调函数则此时调用
        while(this.successCallback.length){
            this.successCallback.shift()(this.value)
        }
    }
    reject = (reason)=>{
        if(this.status != PENDING)  return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
        //如果之前存储了回调函数则此时调用
        while(this.failCallback.length){
            this.failCallback.shift()(this.reason)
        }
    }
    then(successCallback,failCallback){
        let promise2 = new MyPromise((resolve,reject)=>{
            if(this.status == FULFILLED){
                //成功则调用成功以后的回调
                let x = successCallback(this.value)
                resolvePromise(x,resolve,reject)
            }else if(this.status == REJECTED){
                //失败则调用失败后的回调
                let x = failCallback(this.reason)
                resolvePromise(x,resolve,reject)
            }else{
                //pending状态 存储回调函数
                this.successCallback.push(successCallback) 
                this.failCallback.push(failCallback)
            }
        })
        return promise2
    }
}
function resolvePromise(x,resolve,reject){
    if(x instanceof MyPromise){
        //如果是promise
        // x.then(value=>resolve(value),reason=>reject(reason))
        //简写  .then方法会注册回调,resolve是一个函数,.then执行successCallback的时候会传递this.value作为实参,所以这里可以简写
        x.then(resolve,reject)
    }else{
        //如果是普通值
        resolve(x)
    }
}

//CommonJS模块方案导出
module.exports = MyPromise

image.png

promise的then方法链式调用识别promise对象自返回

在promise的链式调用中,.then函数是会返回一个值的,这个值可能是普通值也可能是promise值,chrome给我提供的promise是不予许返回自己的,会提示循环调用错误。

image.png 同时,我们是可以在第一个then函数返回的对象中调用的时候捕获到这个错误的

image.png

那么在我们的promise中如何实现呢,首先,我们只需要判断第一个then函数的返回值是否跟then函数回调函数的返回值是否相等就可以了。

image.png 但是此时还有一个问题,resolvePromise函数执行时间是在new promise2的构造函数中,这个时候promise2是还没有定义的,那么有如何跟x相比较呢? 很简单,构造函数丢到宏任务代码(setTimeout)中执行就好了

//MyPromise.js

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


class MyPromise {
    constructor(executor) {
        executor(this.resolve, this.reject)
    }
    //初始化为准备状态
    status = PENDING
    //成功的原因
    value = undefined
    //失败的原因
    reason = undefined
    //存储起来的回调
    successCallback = []
    failCallback = []
    resolve = (value) => {
        //状态一旦改变就定型
        if (this.status != PENDING) return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
        //如果之前存储了回调函数则此时调用
        while (this.successCallback.length) {
            this.successCallback.shift()(this.value)
        }
    }
    reject = (reason) => {
        if (this.status != PENDING) return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
        //如果之前存储了回调函数则此时调用
        while (this.failCallback.length) {
            this.failCallback.shift()(this.reason)
        }
    }
    then(successCallback, failCallback) {
        let promise2 = new MyPromise((resolve, reject) => {

            if (this.status == FULFILLED) {
                setTimeout(() => {
                    //成功则调用成功以后的回调
                    let x = successCallback(this.value)
                    resolvePromise(promise2, x, resolve, reject)
                }, 0);
            } else if (this.status == REJECTED) {
                //失败则调用失败后的回调
                let x = failCallback(this.reason)
                resolvePromise(promise2, x, resolve, reject)
            } else {
                //pending状态 存储回调函数
                this.successCallback.push(successCallback)
                this.failCallback.push(failCallback)
            }
        })
        return promise2
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        //如果第一个then函数的返回值跟then函数回调函数的返回值是否相等
        reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
        return
    }
    if (x instanceof MyPromise) {
        //如果是promise
        // x.then(value=>resolve(value),reason=>reject(reason))
        //简写  .then方法会注册回调,resolve是一个函数,.then执行successCallback的时候会传递this.value作为实参,所以这里可以简写
        x.then(resolve, reject)
    } else {
        //如果是普通值
        resolve(x)
    }
}

//CommonJS模块方案导出
module.exports = MyPromise

image.png

注意此时的setTimeout写在if判断逻辑内,如果包住整个构造器,就不会走到pending状态存储回调函数这一步,最后在p1.then的错误回调执行的时候打印的也是undefine

Promise异常处理

promise有可能在构造函数中发生错误,也有可能在成功回调或者失败回调中发生错误,所以这三部分我们都应该try catch一下。刚才我们只在成功的回调里面用setTimeOut处理了返回值是promise的情况,那么这里我们也顺便处理下失败回调的。


//index.js

const MyPromise = require('./MyPromise')

let promise = new MyPromise((resolve,reject)=>{
    throw new Error("构造函数错误")
    // reject("失败")
})

promise.then(res=>{
    console.log(res)
},err=>{
    console.log(err.message)
    throw new Error("第一个错误回调函数返回的Error对象")
}).then(res=>{
    console.log('第二个then:'+res)
},reason=>{
    console.log('第二个reject:'+reason)
})
//MyPromise.js

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


class MyPromise {
    constructor(executor) {
        try {
            executor(this.resolve, this.reject)
        } catch (error) {
            this.reject(error)
        }
    }
    //初始化为准备状态
    status = PENDING
    //成功的原因
    value = undefined
    //失败的原因
    reason = undefined
    //存储起来的回调
    successCallback = []
    failCallback = []
    resolve = (value) => {
        //状态一旦改变就定型
        if (this.status != PENDING) return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
        //如果之前存储了回调函数则此时调用
        while (this.successCallback.length) {
            this.successCallback.shift()(this.value)
        }
    }
    reject = (reason) => {
        if (this.status != PENDING) return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
        //如果之前存储了回调函数则此时调用
        while (this.failCallback.length) {
            this.failCallback.shift()(this.reason)
        }
    }
    then(successCallback, failCallback) {
        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status == FULFILLED) {
                setTimeout(() => {
                    try {
                        //成功则调用成功以后的回调
                        let x = successCallback(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                    
                }, 0);
            } else if (this.status == REJECTED) {
                //失败则调用失败后的回调
                setTimeout(() => {
                    try {
                        let x = failCallback(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            } else {
                //pending状态 存储回调函数
                this.successCallback.push(successCallback)
                this.failCallback.push(failCallback)
            }
        })
        return promise2
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        //如果第一个then函数的返回值跟then函数回调函数的返回值是否相等
        reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
        return
    }
    if (x instanceof MyPromise) {
        //如果是promise
        // x.then(value=>resolve(value),reason=>reject(reason))
        //简写  .then方法会注册回调,resolve是一个函数,.then执行successCallback的时候会传递this.value作为实参,所以这里可以简写
        x.then(resolve, reject)
    } else {
        //如果是普通值
        resolve(x)
    }
}

//CommonJS模块方案导出
module.exports = MyPromise

看一下此时的控制台输出

image.png 此时还没有完,因为构造器函数异步的情况还没有考虑,在.then函数中最后返回的是一个新的promise2,如果异步不考虑,这里将无法链式调用

image.png 明明在使用的时候,在第一个.then函数中返回了却无法链式调用。所以这里pending状态我们也要修改

//MyPromise.js
then(successCallback, failCallback) {
        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status == FULFILLED) {
                setTimeout(() => {
                    try {
                        //成功则调用成功以后的回调
                        let x = successCallback(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                    
                }, 0);
            } else if (this.status == REJECTED) {
                //失败则调用失败后的回调
                setTimeout(() => {
                    try {
                        let x = failCallback(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            } else {
                //pending状态 存储回调函数
                this.successCallback.push(()=>{
                    setTimeout(() => {
                        try {
                            //成功则调用成功以后的回调
                            let x = successCallback(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                })
                this.failCallback.push(()=>{
                    setTimeout(() => {
                        try {
                            let x = failCallback(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                })
            }
        })
        return promise2
    }

此时pending状态的异步处理完毕,但是要注意,这里pending状态.push的是一个没有形参的箭头函数,所以上面resolve和reject函数的调用需要改变下

resolve = (value) => {
        //状态一旦改变就定型
        if (this.status != PENDING) return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
        //如果之前存储了回调函数则此时调用
        while (this.successCallback.length) {
            //去掉形参
            this.successCallback.shift()()
        }
    }
    reject = (reason) => {
        if (this.status != PENDING) return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
        //如果之前存储了回调函数则此时调用
        while (this.failCallback.length) {
            this.failCallback.shift()()
        }
    }

此时再看下控制台

image.png

将.then方法参数变为可选

我们平常调用.then的时候,有时候并不会在链式中全部都注册回调函数例如这样

image.png 但是此时,我们第三个回调函数并没有执行,所以我们在在自定义的promise中也要处理下可选参数情况:

        successCallback = successCallback ? successCallback : value => value
        failCallback = failCallback ? failCallback : (reason)=>{throw reason}

调用方式还是刚才那幅图的样子

image.png

Promise.all的实现

all方法也是我们常用的一个API,从使用层面,我们传入一个数组,然后返回一个已经改变了状态的promise,所以我们可以在后面链式调用.then函数,而且.then函数里面的回调函数的形参也是all方法返回的已解析完的数据。以下代码在Mypromise类中添加

 //all肯定是个静态方法,因为可以用MyPromise.all调用,且实参是个数组
    static all(array){
        let result = []
        function addRes(key,val){
            result[key] = val
        }
        return new MyPromise((resolve,reject)=>{
            //要循环遍历传入的数组
            for(let i=0;i<array.length;i++){
                let current = array[i]
                if(current instanceof MyPromise){
                    //如果是promise
                    current.then(val=> addRes(i,val),reason=>reject(reason))
                }else{
                    addRes(i,current)
                }
            }
            resolve(result)
        })
    }

调用代码如下

const MyPromise = require('./MyPromise')

function p1(){
    return new MyPromise((resolve,reject)=>{
        setTimeout(() => {
            resolve("success")
        }, 2000);
    })
}

function p2(){
    return new MyPromise((resolve,reject)=>{
        resolve("success2")
    })
}

MyPromise.all(['1','2',p1(),p2(),'5']).then(res=>{
    console.log(res)
})

让我们看看效果:

image.png 可以看到异步的p1在返回的数组中是一个空值,因为for循环是同步执行的,所以这里我们需要处理下,兼容下异步的情况:

//MyPromise.js


//all肯定是个静态方法,因为可以用MyPromise.all调用,且实参是个数组
    static all(array){
        return new MyPromise((resolve,reject)=>{
            let result = []
            let index = 0
            function addRes(key,val){
                result[key] = val
                index ++
                //兼容异步处理
                if(index == array.length){
                    resolve(result)
                }
            }
            //要循环遍历传入的数组
            for(let i=0;i<array.length;i++){
                let current = array[i]
                if(current instanceof MyPromise){
                    //如果是promise
                    current.then(val=> addRes(i,val),reason=>reject(reason))
                }else{
                    addRes(i,current)
                }
            }
        })
    }

我们再看下调用结果:

image.png 这样就达到期望值了。

Promise的resolve方法

Promise.resolve()方法也是我们常用的一个API,用来把传入的值变成一个已经resolve的promise对象,如果传入的就是一个promise对象,则原封不动的返回。这样就可以在后面链式调用.then函数了。既然可以用类名调用,那肯定也是一个static对象。

//MyPromise.js
//在类中添加下面的静态方法
static resolve(val){
        if(val instanceof MyPromise){
            return val
        }else{
            //若是普通值
            return new Promise(resolve=>{
                resolve(val)
            })
        }
    }

看下效果图:

image.png

Promise.finally

‘finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。 finally方法的回调函数不接受任何参数,这意味着没有办法知道,前面的 Promise 状态到底是fulfilled还是rejected。这表明,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。’以上来自阮一峰老师的教程,finally其实就是.then方法的特例。

//MyPromise.js
//在类中添加原型方法

finally(callback){
        //因为在finally后面还可以继续链式调用,所以此处应该返回一个promise
        //又因为.then方法本来就返回一个promise,且可以获得finally时的promise状态
        return this.then((val)=>{
             callback()
             return val
        },(reason)=>{
            callback()
            throw reason
        })
    }
//index.js

const MyPromise = require('./MyPromise')

function p1(){
    return new MyPromise((resolve,reject)=>{
        setTimeout(() => {
            resolve("success")
        }, 2000);
    })
}

function p2(){
    return new MyPromise((resolve,reject)=>{
        resolve("success2")
    })
}

p2().finally(()=>{
    // console.log('finally')
    throw new Error('finally err')
}).then(res=>{
    console.log("resolve:" + res)
},reason=>{
    console.log('reason:' + reason)
})

看一下调用效果

image.png 如果你在finally函数中返回p1呢?

image.png 这里直接走到p2的resolve去了,后面的.then也是根据p2的resolve状态来的。因为在finally函数中callback执行完以后就直接执行到return val这一句了,callback中的异步

setTimeout(() => {
            resolve("success")
        }, 2000);

并没有被finally返回,只是被挂起到异步队列中。那么此处我们可以用static的resolve方法来改写下,因为resolve方法刚好可以处理返回值是promise的情况

finally(callback){
        //因为在finally后面还可以继续链式调用,所以此处应该返回一个promise
        //又因为.then方法本来就返回一个promise,且可以获得finally时的promise状态
        return this.then((val)=>{
            return MyPromise.resolve(callback()).then(()=>val)
        },(reason)=>{
            return MyPromise.resolve(callback()).then(()=>{throw reason})
        })
    }

image.png 因为p1等待两秒才改变状态,所以两秒钟后输出console.log("resolve:" + res)

Promise.catch

Promise.catch方法其实就是.then(undefined,failCallback)的别名,所以实现可以如下:

catch(failCallback){
        return this.then(undefined,failCallback)
    }

效果如图:

image.png

完整实现代码:

//index.js

const MyPromise = require('./MyPromise')

function p1(){
    return new MyPromise((resolve,reject)=>{
        setTimeout(() => {
            resolve("success")
        }, 2000);
    })
}

function p2(){
    return new MyPromise((resolve,reject)=>{
        // resolve("success2")
        reject('fail')
    })
}


p2().then(res=>{console.log("res:" + res)}).catch(res=>{
    console.log('catch:'+res)
})
//MyPromise.js

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


class MyPromise {
    constructor(executor) {
        try {
            executor(this.resolve, this.reject)
        } catch (error) {
            this.reject(error)
        }
    }
    //初始化为准备状态
    status = PENDING
    //成功的原因
    value = undefined
    //失败的原因
    reason = undefined
    //存储起来的回调
    successCallback = []
    failCallback = []
    resolve = (value) => {
        //状态一旦改变就定型
        if (this.status != PENDING) return
        this.status = FULFILLED
        //保存成功以后的值
        this.value = value
        //如果之前存储了回调函数则此时调用
        while (this.successCallback.length) {
            this.successCallback.shift()()
        }
    }
    reject = (reason) => {
        if (this.status != PENDING) return
        this.status = REJECTED
        //保存失败以后的值
        this.reason = reason
        //如果之前存储了回调函数则此时调用
        while (this.failCallback.length) {
            this.failCallback.shift()()
        }
    }
    then(successCallback, failCallback) {
        successCallback = successCallback ? successCallback : value => value
        failCallback = failCallback ? failCallback : (reason)=>{throw reason}
        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status == FULFILLED) {
                setTimeout(() => {
                    try {
                        //成功则调用成功以后的回调
                        let x = successCallback(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            } else if (this.status == REJECTED) {
                //失败则调用失败后的回调
                setTimeout(() => {
                    try {
                        let x = failCallback(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (error) {
                        reject(error)
                    }
                }, 0);
            } else {
                //pending状态 存储回调函数
                this.successCallback.push(()=>{
                    setTimeout(() => {
                        try {
                            //成功则调用成功以后的回调
                            let x = successCallback(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                })
                this.failCallback.push(()=>{
                    setTimeout(() => {
                        try {
                            let x = failCallback(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (error) {
                            reject(error)
                        }
                    }, 0);
                })
            }
        })
        return promise2
    }
    finally(callback){
        //因为在finally后面还可以继续链式调用,所以此处应该返回一个promise
        //又因为.then方法本来就返回一个promise,且可以获得finally时的promise状态
        return this.then((val)=>{
            return MyPromise.resolve(callback()).then(()=>val)
        },(reason)=>{
            return MyPromise.resolve(callback()).then(()=>{throw reason})
        })
    }
    catch(failCallback){
        return this.then(undefined,failCallback)
    }
    //all肯定是个静态方法,因为可以用MyPromise.all调用,且实参是个数组
    static all(array){
        return new MyPromise((resolve,reject)=>{
            let result = []
            let index = 0
            function addRes(key,val){
                result[key] = val
                index ++
                if(index == array.length){
                    resolve(result)
                }
            }
            //要循环遍历传入的数组
            for(let i=0;i<array.length;i++){
                let current = array[i]
                if(current instanceof MyPromise){
                    //如果是promise
                    current.then(val=> addRes(i,val),reason=>reject(reason))
                }else{
                    addRes(i,current)
                }
            }
        })
    }
    static resolve(val){
        if(val instanceof MyPromise){
            return val
        }else{
            //若是普通值
            return new Promise(resolve=>{
                resolve(val)
            })
        }
    }
}
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        //如果第一个then函数的返回值跟then函数回调函数的返回值是否相等
        reject(new TypeError('Chaining cycle detected for promise #<Promise>'))
        return
    }
    if (x instanceof MyPromise) {
        //如果是promise
        // x.then(value=>resolve(value),reason=>reject(reason))
        //简写  .then方法会注册回调,resolve是一个函数,.then执行successCallback的时候会传递this.value作为实参,所以这里可以简写
        x.then(resolve, reject)
    } else {
        //如果是普通值
        resolve(x)
    }
}

//CommonJS模块方案导出
module.exports = MyPromise