彻底理解Promise(上)---原理篇

697 阅读10分钟

Promise的理解

Promise是一种异步编程的解决方案,用于表示一个异步操作的最终完成 (或失败)及其结果值, 一个 Promise 对象代表一个在这个 promise 被创建出来时不一定已知的值。它让你能够把异步操作最终的成功返回值或者失败原因和相应的处理程序关联起来。 这样使得异步方法可以像同步方法那样返回值:异步方法并不会立即返回最终的值,而是会返回一个 promise,以便在未来某个时候把值交给使用者(摘自MDN

Promise解决了哪些问题

  1. 回调嵌套导致代码难以维护问题,增强代码的可读性
  2. 解决异步问题(如:异步方法返回值与同步方法处理逻辑无法关联、异步返回值之间相互关联的问题)
  3. 控制并发请求

如何实现一个Promise

下面一步步的实现一个promise,首先明确几个实现的核心概念

  1. resolve与reject方法实现
  2. 状态凝固,即pending->fulfilled或者pending->rejected,状态一旦改变便不可更改
  3. then方法的实现,如何实现链式回调
  4. then传入参数处理
  5. then返回值处理
  6. 解决异步问题

一、先来一个基础类‘框架’

```
const isFunction = fn => typeof fn === 'function'
class MyPromise {
    constructor(execute) {
        // 验证参数是否为函数
        if (!isFunction(execute)) {
            throw new TypeError("params is not Function")
        }
        // 初始化相关参数
        this.initValue()
        // 执行函数, 使用bind改变this指向,或者使用箭头函数编写resolve与reject函数
        execute(this.resolve.bind(this), this.reject.bind(this))
    }
    initValue () {
        this.state = "pending" // 状态值
        this.value = null  // 成功信息
        this.reason = null // 失败信息
    }
    resolve (value) {
        console.log(value)
    }
    reject (reason) {
        console.log(reason)
    }

}
new MyPromise(( resolve, reject ) => {
    resolve(12)
})

这样我们就简单的实现了resolve与reject函数,为了使状态凝固,我们需要在这两个函数的第一步逻辑中加入状态的判段,即当为pending状态的时候才去做处理的逻辑,其他状态则进行忽略处理

resolve (value) {
    if (this.state !== 'pending') return
    this.value = value 
    this.state = 'fulfilled' // 更改状态值
}
reject (reason) {
    if (this.state !== 'pending') return
    this.reason = reason
    this.state = 'rejected'
}

上面初步完成了resolve与reject方法,下面来实现then方法,then方法在Promise非常的核心,then方法实现后,其他的Promise静态方法大部分都是then方法的一种衍生,既然要实现then方法的链式回调,那么无非就两种办法,一种是返回this,一种是重新实例化对象返回,这里创建一个没有经过回调函数处理的新 Promise 对象,这个新 Promise 只是简单地接受调用这个 then 的原 Promise 的终态作为它的终态,

then (onFulfilledFun, onRejectedFun) {
    let { state, reason, value } = this
    return  new MyPromise((resolve, reject)=> {
        // 成功调用函数
        if (state === 'fulfilled') {
           resolve(onFulfilledFun(value))
        }
        // 失败调用函数
        if (state === 'rejected') {
            reject(onRejectedFun(reason))
        }

    })
}

到这里我们基本上已经实现了一个简易版的Promise,运行试下结果

new MyPromise(( resolve, reject ) => {
    resolve(12)
}).then(res=> {
    console.log(res)
    return 'a'
}).then(r=> {
    console.log(r)
})

完美输出: image.png 接下来我们添加模拟异步操作的setTimeout再试一下

new MyPromise(( resolve, reject ) => {
    setTimeout(()=> {
        console.log('setTimeout')
        resolve(12)
    }, 1000)
}).then(res=> {
    console.log(res)
    return 'a'
}).then(r=> {
    console.log(r)
})

结果如下:

image.png

咦!!!,我的then方法怎么没有执行! 为什么会出现这种情况,是因为setTimeout的出现使得我们then函数里面的同步逻辑优先执行了,两个then的调用都是处于一种pending的状态,代码如下

then (onFulfilledFun, onRejectedFun) {
    let { state, reason, value } = this
    return  new MyPromise((resolve, reject)=> {
        // 成功调用函数
        if (state === 'fulfilled') {
            resolve(onFulfilledFun(value))
        }
        // 失败调用函数
        if (state === 'rejected') {
            reject(onRejectedFun(reason))
        }
        // pending状态调用
        if (state === 'pending') {
            console.log(123)
        }
    })
}

输出结果: 123、123 输出了两次,程序走进了我们的pending逻辑,由于状态值state是在resolve函数中更改的,所有在设定的时间之前state状态没有改变,所以都走进了pending的逻辑中,对于这种情况我们使用模拟队列的方式来进行解决,把我们pending状态的操作放入到相应的队列里面,在resolve或者reject函数中依次执行

  • 在初始化函数initValue中声明存储队列的函数
this.fulfilledQueues = []  // fulfilled状态队列
this.rejectedQueues = []   // rejected状态队列
  • 在then函数中填入队列相关的操作
then (onFulfilledFun, onRejectedFun) {
    let { state, reason, value } = this
    return  new MyPromise((resolve, reject)=> {
        // 成功调用函数
        if (state === 'fulfilled') {
            resolve(onFulfilledFun(value))
        }
        // 失败调用函数
        if (state === 'rejected') {
            reject(onRejectedFun(reason))
        }
        if (state === 'pending') {
        
           this.fulfilledQueues.push( value => resolve(onFulfilledFun(value)))
           this.rejectedQueues.push(value => reject(onRejectedFun(value)))

        }
    })
}
  • 成功按顺序输出:

image.png

  • 这时我们还没有对then函数传入的参数做判段
  • 此时有两种情况:
  1. 传入期望的函数 (处理: 运行函数)
  2. 传入非函数 (处理:忽略传入值,取上次成功改变的value值,即值穿透问题)
  • 处理完传入值 还有onFulfilledFun或者onRejectedFun返回值的处理
  • 此时有三种情况:
  1. 返回普通值
  2. 返回一个新的Promise或者thenable数据结构
  3. 返回自身即循环引用问题 (原生Promise里面循环引用会报错)
  • 下面对then 函数进行改造
then (onFulfilledFun, onRejectedFun) {
    let { state, reason, value } = this
    const _myPromise = new MyPromise((resolve, reject)=> {

        // 成功处理函数
        const resolveFun = value => {
            // 判断传入不是函数是进行忽略,取上次调用的值或者resolve的值
            if (!isFunction(onFulfilledFun)) {
                resolve(value)
            } else {
                let result = onFulfilledFun(value)
                // 判断返回值是不是自身,解决循环调用问题
                if (result === _myPromise) {
                    throw new TypeError('Loop call, 循环调用')
                }
                // 返回值为promise时的处理
                else if (result instanceof MyPromise) {
                    result.then(resolve, reject)
                }
                else {
                    resolve(result)
                }
            }
        }
        // 失败处理函数
        const rejectFun = value => {
            if (!isFunction(onRejectedFun)) {
                reject(value)
            } else {
                let result = onRejectedFun(value)
                // 判断返回值是不是自身,解决循环调用问题
                if (result === _myPromise) {
                    throw new TypeError('Loop call, 循环调用')
                }
                // 返回值为promise时的处理
                else if (result instanceof MyPromise) {
                    result.then(resolve, reject)
                }
                else {
                    reject(result)
                }
            }
        }
        switch (state) {
            case "pending":
                this.fulfilledQueues.push(resolveFun)
                this.rejectedQueues.push(rejectFun)
                break;
            case "fulfilled":
                onFulfilledFun(value)
                break;
            case "rejected":
                onRejectedFun(reason)
                break
            default:
                throw new Error("nothing")
        }
    })
    return _myPromise
}
  • 下面进行测试
new MyPromise(( resolve, reject ) => {
    setTimeout(()=> {
        console.log('setTimeout')
        resolve(12)
    }, 1000)
}).then("abc").then(res=> {
    console.log('then1:',res)
    return new MyPromise(resolve => resolve("then1-1"))
}).then(r=> {
    console.log('then2:', r)
})
  • 结果按照预期输出, 对then函数参数不是函数的情况也进行了忽略 image.png

  • 下面测试下循环引用问题

let p = new MyPromise(( resolve, reject ) => {
    setTimeout(()=> {
        console.log('setTimeout')
        resolve(12)
    }, 1000)
})
let p1 = p.then(r => {
    console.log(123)
    return p1
})
  • 符合预期的输出

image.png

感觉要大功告成了啊,下面再次回测一下去掉setTimieout情况,同步代码中直接resolve

new MyPromise(( resolve, reject ) => {
    resolve(12)
}).then("abc").then(res=> {
    console.log('then1:',res)
    return new MyPromise(resolve => resolve("then1-1"))
}).then(r=> {
    console.log('then2:', r)
})
  • 结果如下

image.png

竟然报错了!!!,那就把then("abc")去掉在输出一遍

image.png

呀!这是什么情况,then2竟然没有输出!赶紧分析下代码 报错分析:首先resolve的代码是同步的,将状态改变后直接走到then("abc")的链式调用逻辑中,这时state状态是fulfilled,此时then函数的参数是非函数, 程序走进了 case "fulfilled": 所以会报错

  • 至于then2为啥没有输出,先打印下then函数中接收的状态值

image.png

我们可以看到在第一个then函数执行完后状态值变成了pending,此时第二个then函数的操作在成功的队列中,但他并没有执行, 所以不会输出,也可以理解为,resolve函数执行的时候,成功的队列是空的,后面的链式调用添加进队列的时间晚于resolve函数的执行。那应该如何解决呢?那就要解决链式调用在resolve之前添加进队列,下面对resolve函数进行改造,只需要加一个延时器即可

resolve (value) {
   const resolveRun = () => {
       if (this.state !== 'pending') return
       this.value = value
       this.state = 'fulfilled'
       this.fulfilledQueues.forEach(fn => fn(this.value))
   }
   setTimeout(resolveRun, 0)
}

看下输出结果:

image.png

符合预期,咦,肯定有人问这是为啥?这是因为加了延时器之后,resolve的函数逻辑被放在了事件循环的最后,链式调用函数依次执行,此时状态值都是pending所以都被添加进了队列,当我们再次执行resolve函数的时候,队列数组里面就存在了各个链式调用的函数。

-resolve 函数参数的处理 这里还有一种情况就是当我们resolve的参数为Promise实例或者是thenable的时候怎么处理,即为: resolve(new MyPromise(resolve => resolve('test')))

这时我们需要在resolve函数中加入判断

if (value instanceof MyPromise) {
    value.then(v => {
        this.value = v
        this.fulfilledQueues.forEach(fn => fn(v))
    }, err => {
        this.reason = err
        this.rejectedQueues.forEach(fn => fn(err))
    })

} //thenable 判断
else if (value && value.then && isFunction(value.then)) {
    new MyPromise(value.then).then(res=> {
       this.value = res
       this.fulfilledQueues.forEach(fn => fn(this.value))
    })

} //其他情况
else {
    this.value = value
    this.fulfilledQueues.forEach(fn => fn(value))
}

这样我们的resolve函数就可以支持多种类型的参数了

new MyPromise(( resolve, reject ) => {

    resolve(new MyPromise(r => r('hello')))
    resolve(thenable)
    resolve(123)
}).then(res=> {
    console.log('then1:',res)
   
})

上述代码测试三种情况都能按预期输出。

  • 到这里我们已经基本实现了核心的功能, 再来实现其他的方法

.catch实现

  • catch方法其实就是不传入then函数的第一个参数
catch (onRejected) {
    return this.then(null, onRejected)
}

.finally实现

finally描述传送门

finally (onFinally) {
  // 通过获取构造函数,调用静态方法resolve
  let pConst = this.constructor
  return this.then(
      value => pConst.resolve(onFinally()).then(() => value),
      reason => pConst.resolve(onFinally()).then(() => { throw reason })
      )
}

.resolve实现

  • 静态方法resolve返回的是一个Promise包装过的对象,它的参数也分为三种情况
  1. thenable
  2. Promise实例
  3. 普通值
static resolve (value) {
    // 如果参数是MyPromise实例,直接返回这个实例
    if (value instanceof MyPromise) return value
    // 如果参数是thenable对象 
    if (value && value.then && isFunction(value.then))  return new MyPromise(value.then)
    // 其他情况,经过MyPromise包装后返回
    return new MyPromise(resolve => resolve(value))
}

.reject实现

static reject (err) {
    return new MyPromise((resolve, reject) => reject(err))
}

.all实现

  • 有序输出
return new MyPromise((resolve, reject) => {
    let len = promiseList.length
    let result = []
   promiseList.reduce((pre, next) => {
        return pre.then(next).then(res => {
            result.push(res)
            result.length === len && resolve(result)
        },error => reject(error))
    }, MyPromise.resolve())
})
  • 无序输出
return new MyPromise((resolve, reject) => {
    let len = promiseList.length
    let result = []
    for (let i = 0; i < len ; i++) {
        let pItem = promiseList[i]
        pItem.then(res => {
            result.push(res)
            result.length === len && resolve(result)
        }, error => {
            reject(error)
        })

    }
})

.race实现

static race (promiseList) {
    return new MyPromise((resolve, reject) => {
        promiseList.forEach(item => {
            item.then(res=> resolve(res), error => reject(error))
        })
    })
}   

到此大部分功能都已经实现了,下面是完整的代码:

const isFunction = fn => typeof fn === 'function'

class MyPromise {
    constructor(execute) {
        // 验证参数是否为函数
        if (!isFunction(execute)) {
            throw new TypeError("params is not Function")
        }
        // 初始化相关参数
        this.initValue()
        // 执行函数, 使用bind改变this指向,或者使用箭头函数编写resolve与reject函数
        execute(this.resolve.bind(this), this.reject.bind(this))
    }
    initValue () {
        this.state = "pending" // 状态值
        this.value = null  // 成功信息
        this.reason = null // 失败信息
        this.fulfilledQueues = []  // fulfilled状态队列
        this.rejectedQueues = []   // rejected状态队列
    }
    loopFun (queues, v) {
        queues.forEach(fn => fn(v))
    }
    resolve (value) {
       const resolveRun = () => {
           if (this.state !== 'pending') return
           this.state = 'fulfilled'
            // Promise实例判断
           if (value instanceof MyPromise) {
               value.then(v => {
                   this.value = v
                   this.loopFun(this.fulfilledQueues, v)
               }, err => {
                   this.reason = err
                   this.loopFun(this.rejectedQueues, err)
               })

           } //thenable 判断
           else if (value && value.then && isFunction(value.then)) {
               new MyPromise(value.then).then(res=> {
                  this.value = res
                   this.loopFun(this.fulfilledQueues, res)
               })
           } //其他情况
           else {
               this.value = value
               this.loopFun(this.fulfilledQueues, value)
           }

       }
       setTimeout(resolveRun, 0)
    }
    reject (reason) {
        const rejectRun = () => {
            if (this.state !== 'pending') return
            this.reason = reason
            this.state = 'rejected'
            this.rejectedQueues.forEach(fn => fn(this.reason))
        }
        setTimeout(rejectRun, 0)
    }
    then (onFulfilledFun, onRejectedFun) {
        let { state, reason, value } = this
        const _myPromise = new MyPromise((resolve, reject)=> {

            // 成功处理函数
            const resolveFun = value => {
                // 判断传入不是函数是进行忽略
                if (!isFunction(onFulfilledFun)) {
                    resolve(value)
                } else {
                    let result = onFulfilledFun(value)
                    // 判断返回值是不是自身,解决循环调用问题
                    if (result === _myPromise) {
                        throw new TypeError('Loop call, 循环调用')
                    }
                    // 返回值为promise时的处理
                    else if (result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                    else {
                        resolve(result)
                    }
                }
            }
            // 失败处理函数
            const rejectFun = value => {
                if (!isFunction(onRejectedFun)) {
                    reject(value)
                } else {
                    let result = onRejectedFun(value)
                    // 判断返回值是不是自身,解决循环调用问题
                    if (result === _myPromise) {
                        throw new TypeError('Loop call, 循环调用')
                    }
                    // 返回值为promise时的处理
                    else if (result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                    else {
                        reject(result)
                    }
                }
            }
            switch (state) {
                case "pending":
                    this.fulfilledQueues.push(resolveFun)
                    this.rejectedQueues.push(rejectFun)
                    break;
                case "fulfilled":
                    onFulfilledFun(value)
                    break;
                case "rejected":
                    onRejectedFun(reason)
                    break
                default:
                    throw new Error("nothing")
            }
        })
        return _myPromise
    }
    catch (onRejected) {
        return this.then(null, onRejected)
    }
    finally (onFinally) {
        // 通过获取构造函数,调用静态方法resolve
        let pConst = this.constructor
        return this.then(
            value => pConst.resolve(onFinally()).then(() => value),
            reason => pConst.resolve(onFinally()).then(() => { throw reason })
        )
    }
    static resolve (value) {
        // 如果参数是MyPromise实例,直接返回这个实例
        if (value instanceof MyPromise) return value
        // 如果参数是thenable对象
        if (value && value.then && isFunction(value.then))  return new MyPromise(value.then)
        // 其他情况,经过MyPromise包装后返回
        return new MyPromise(resolve => resolve(value))
    }
    static all (promiseList) {
        // 无序
        return new MyPromise((resolve, reject) => {
            let len = promiseList.length
            let result = []
            for (let i = 0; i < len ; i++) {
                let pItem = promiseList[i]
                pItem.then(res => {
                    result.push(res)
                    result.length === len && resolve(result)
                }, error => {
                    reject(error)
                })

            }
        })
        // 有序
        // return new MyPromise((resolve, reject) => {
        //     let len = promiseList.length
        //     let result = []
        //    promiseList.reduce((pre, next) => {
        //         return pre.then(next).then(res => {
        //             result.push(res)
        //             result.length === len && resolve(result)
        //         },error => reject(error))
        //     }, MyPromise.resolve())
        // })
    }
    static race (promiseList) {
        return new MyPromise((resolve, reject) => {
            promiseList.forEach(item => {
                item.then(res=> resolve(res), error => reject(error))
            })
        })
    }

}

整体的代码还有优化的空间,有理解偏差的地方还请指正,下一篇介绍promise相关的面试题