深入学习Promise

143 阅读5分钟

image.png

前言

本文主要会讲解 PromiseA+ 规范,会实现一个能通过所有 PromiseA+ 规范的测试用例的 Promise,本文不再讲解promise的使用,也会涉及到一些高阶函数和柯里化的使用,需要提前去掌握一下

Promise的状态

  • Promise有三个状态,分别是等待态(PENDING)完成态(RESOLVE)拒绝态(REJECTED)状态初始化为等待态,一旦变为完成或拒绝就无法再修改
const PENDING = 'PENDING'
const RESOLVEED = 'RESOLVEED'
const REJECTED = 'REJECTED'

Promise的初始化属性

  • 实例化必须传入一个方法
  • 初始化状态为 PENDING
  • 初始化一个value、reason 后面用于保存完成的值、拒绝的原因
  • 初始化一个 resolveCallcacks 的数组,用于保存完成后执行的回调
  • 初始化一个 rejectCallcacks 的数组,用于保存拒绝后执行的回调
class Promise {
    constructor(executor) {
        if(typeof executor !== "function") throw Error('传入的参数必须是一个方法')
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        this.resolveCallbacks = []
        this.rejectCallbacks = []
    }
}

Promise 构造器里调用传入的执行方法

  • executor方法会立即执行,方法会有两个入参,第一个是可以转换成完成态的 handleResolve 方法,另一个则是能转换成拒绝态 handleReject 的方法,两个方法可以传入一个value或者reason的参数,用于表示当前的Promise的值或者是拒绝的原因
  • 在执行 executor 方法时报错也会调用 handleReject 进去拒绝态
  • 一旦在 executor 在执行 handleResolve 或者 handleReject 后会进行状态修改、保存参数以及执行通过“实例.then”注册的对应的方法
class Promise {
    constructor(executor) {
        //  ......
         const handleResolve = value => {
            if (this.status === PENDING) {
                this.value = value
                this.status = RESOLVEED
                this.resolveCallbacks.forEach(cb => cb(this.value))
            }
        }

        const handleReject = reason => {
            if (this.status === PENDING) {
                this.reason = reason
                this.status = REJECTETED
                this.rejectCallbacks.forEach(cb => cb(this.reason))
            }
        }
        try {
            executor(handleResolve, handleReject)
        } catch (e) {
            handleReject(e)
        }

    }
}

then的两个方法 onFulfilled 和 onRejected 的初始化处理

  • then方法有两个可选参数是 onFulfilled 和 onRejected 分别对应是完成态的回调和拒绝态的回调,一般情况下这两个是一个方法,方法的第一个参数能取到 Promise的 value 或 reason
  • onFulfilled onRejected 两个可选参数如果没传或者是传入一个非方法类型的值,会转成一个方法,前者将Promise的Value向下进行传递,后者会抛出一个错误(其实也是属于向下传递)
class Promise {
    constructor(executor) {
        // ......
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : err => {
            throw err
        }
    }
}

Promise 链式操作、回调注册

  • Promise 是利用返回了一个新的Promise实现链式调用

  • 在执行 onFulfilled 或者 onRejected 会以trycatch进行捕捉错误,如果没有错误则将 onFulfilled的返回值作为 nextResolve进行调用(往下执行),有错误则将捕获的错误作为reason进行调用 nextReject(柯里化封装一个通用的处理回调函数的方法)

  • 根据当前的状态,合粒化 RESOLVE 则会执行 onFulfilled 否则则会执行 onRejected 同时传入相应 value 或 reason
  • 如果当前状态是等待态的话,会将执行的回调存到 resolveCallbacks 和 rejectCallbacks
  • 如果当前是完成态或拒绝态(也就是同步执行的话),那就直接调用 onFulfilled 或 onRejected
class Promise {
    constructor(executor) {
        // ......
    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : err => {
            throw err
        }
        return new Promise((nextResolve, nextReject) => {
            const publicFunc = handleCallback => valueOrReason => {
                try{
                    const x = handleCallback(valueOrReason)
                    nextResolve(x)
                }catch (e) {
                    nextReject(e)
                }
            }

            switch (this.status) {
                case PENDING:
                    // 这里的 value 或者 reason 是由构造器中 handleResolve handleReject方法调用传入
                    this.resolveCallbacks.push(publicFunc(onFulfilled))
                    this.rejectCallbacks.push(publicFunc(onRejected))
                    break
                case RESOLVEED:
                    publicFunc(onFulfilled)(this.value)
                    break
                case REJECTED:
                    publicFunc(onRejected)(this.reason)
                    break
            }
        })
    }
}

处理 onFulfilled 和 onRejected 返回值

  • 实现一个 resolvePromise 方法并传入返回的promise,x ,nextResolve,nextReject进行统一处理
  • 由于在 new 实例过程中,constructor调用时实例还未返回,所以使用一个 setTimeout 方法进行处理,可以异步获取到 new 之后的实例
class Promise {
    constructor(executor) {
        // ......
    }

    then(onFulfilled, onRejected) {
        // ......
        const promise2 = new Promise((nextResolve, nextReject) => {
            const publicFunc = handleCallback => valueOrReason => {
                setTimeout(() => {
                    try {
                        const x = handleCallback(valueOrReason)
                        resolvePromise(promise2, x, nextResolve, nextReject)
                    } catch (e) {
                        nextReject(e)
                    }
                }, 0)

            }

            switch (this.status) {
               //......
            }
        })
        return promise2
    }
}

  • 由于返回值的 x 可能也是一个 Promise 实例,并且如果返回的实例跟返回值 x 是同一个实例,就会出现循环等待,在 Promise 规范里会以将这个类型错误作为返回 nextReject 的值

  • 在 Promise 规范中如果当前是一个对象(包含数组和方法)并且有 then 方法,则会认为这是一个 Promise 实例,就会直接调用 then 方法,并且如果 then 后还是一个promise,就会实现一个递归调用,直到找到 resolve 的值,或者是 reject 的拒因

  • 并且为了防止会出现重复调用 nextResolve 和 nextReject ,使用一个 called 变量
const resolvePromise = (promise, x, resolve, reject) => {
    if (promise === x) return reject(new TypeError('当前会引起循环等待'))

    let called;
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        try {
            let then = x.then
            if (typeof then === "function") {
                // promise
                then.call(x, y => {
                    if (called) return
                    called = true
                    resolvePromise(promise, y, resolve, reject)
                }, err => {
                    if (called) return
                    called = true
                    reject(err)
                })

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

使用 promises-aplus-tests 运行测试用例对实现的 Promise进行测试

// 第一步: 下载
npm i promises-aplus-tests -g

// 第二步在 promise.js 下方添加下面的代码
class Promise{
    // ......
}

Promise.defer = Promise.deferred = function () {
    const dfd = {}
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve
        dfd.reject = reject
    })

    return dfd
}

module.exports = Promise

// 第三步在命令行中  后面是你当前的文件路径
promises-aplus-tests ./promise.js

完整代码

const PENDING = 'PENDING'
const RESOLVEED = 'RESOLVEED'
const REJECTED = 'REJECTED'

const resolvePromise = (promise2, x, nextResolve, nextReject) => {
    if (promise2 === x) return nextReject(new TypeError('当前会引起循环等待'))

    let called;
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        try {
            let then = x.then
            if (typeof then === "function") {
                // promise
                then.call(x, y => {
                    if (called) return
                    called = true
                    resolvePromise(promise2, y, nextResolve, nextReject)
                }, err => {
                    if (called) return
                    called = true
                    nextReject(err)
                })

            } else {
                nextResolve(x)
            }
        } catch (e) {
            if (called) return
            called = true
            nextReject(e)
        }
    } else {
        nextResolve(x)
    }
}

class Promise {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        this.resolveCallbacks = []
        this.rejectCallbacks = []
        if (typeof executor !== "function") throw Error('出错')

        const handleResolve = value => {
            if (this.status === PENDING) {
                this.value = value
                this.status = RESOLVEED
                this.resolveCallbacks.forEach(cb => cb(this.value))
            }
        }

        const handleReject = reason => {
            if (this.status === PENDING) {
                this.reason = reason
                this.status = REJECTED
                this.rejectCallbacks.forEach(cb => cb(this.reason))
            }
        }

        try {
            executor(handleResolve, handleReject)
        } catch (e) {
            handleReject(e)
        }

    }

    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : val => val
        onRejected = typeof onRejected === 'function' ? onRejected : err => {
            throw err
        }
        const promise2 = new Promise((nextResolve, nextReject) => {
            const publicFunc = handleCallback => valueOrReason => {
                setTimeout(() => {
                    try {
                        const x = handleCallback(valueOrReason)
                        resolvePromise(promise2, x, nextResolve, nextReject)
                    } catch (e) {
                        nextReject(e)
                    }
                }, 0)

            }

            switch (this.status) {
                case PENDING:
                    this.resolveCallbacks.push(publicFunc(onFulfilled))
                    this.rejectCallbacks.push(publicFunc(onRejected))
                    break
                case RESOLVEED:
                    publicFunc(onFulfilled)(this.value)
                    break
                case REJECTED:
                    publicFunc(onRejected)(this.reason)
                    break
            }
        })
        return promise2
    }
}

Promise.defer = Promise.deferred = function () {
    const dfd = {}
    dfd.promise = new Promise((resolve, reject) => {
        dfd.resolve = resolve
        dfd.reject = reject
    })

    return dfd
}

module.exports = Promise