手写一个自己的Promise(二)

系列文章目录

根据PromiseA+规范,自己手动实现一个Promise



前言

在上一篇文章中,我们已经实现了MyPromise构造器部分的相关代码,本篇文章则着重实现then方法的回调执行时机


一、then方法

根据promise A+规范中的描述总结,大致归纳为下述内容

  • onFulfilled(可选):一个在此 Promise 对象被兑现时异步执行的函数。它的返回值将成为 then() 返回的Promise对象的兑现值。此函数被调用时将传入参数value(代表Promise 对象的兑现值)。
  • onRejected(可选):一个在此Promise 对象被拒绝时异步执行的函数。它的返回值将成为 catch() 返回的 Promise对象的兑现值。此函数被调用时将传入参数reason(表示Promise 对象被拒绝的原因)。
  • 返回值:立即返回一个新的 Promise 对象。

二、开始手动实现

思路:
首先,创建一个then方法,它接收两个函数作为参数,分别是onFulfilled和onRejected,之后返回一个MyPromise实例,在实例中,通过判断此时MyPromise的状态是成功态(resolve)还是失败态(reject)来决定具体是执行onFulfilled还是onRejected,无论执行哪个回调,都会将当前promise的结果作为参数传进去。
根据上述思路可以完成下面的代码

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

class MyPromise {

    #state = PENDING
    #result = undefined

    constructor(executor) {
        const resolve = (data) => {
            this.#changeState(FULFILLED, data)
        }
        const reject = (reason) => {
            this.#changeState(REJECTED, reason)
        }
        try {
            executor(resolve, reject)
        } catch (error) {
            reject(error)
        }
    }
    #changeState(state, result) {
        if (this.#state !== PENDING) return
        this.#state = state
        this.#result = result
    }

    then(onFulfilled, onRejected) {  // onFulfilled和onRejected是外部通过then方法传入的成功与失败对应的执行函数
        return new MyPromise((resolve, reject) => { // resolve与reject是当前返回的MyPromise实例的成功与失败回调
            if (this.#state === FULFILLED) { // 如果是成功,执行onFulfilled函数
                onFulfilled(this.#result)
            } else if (this.#state === REJECTED) // 如果是失败,执行onRejected函数
                onRejected(this.#result)
        })
    }
}


根据上述思路执行一下代码,会发现控制台能够正常输出的。(状态是pending没变化是正常的,因为还没写到修改状态的步骤)
此时控制台的输出结果
但如果此时是异步传入结果呢?
请添加图片描述

通过一个setTimeout将resolve包裹起来就会发现,控制台没有输出了,这是因为是此时是异步传入结果,所以当知道可以具体执行哪个回调函数时,then方法的执行早就已经结束了,自然也就没办法再次执行具体的回调函数了。

解决思路:
要解决上面的问题也很简单,就是在then方法中不再直接执行具体的回调,而是先定义一个参数handlers将onFulfilled, onRejected,resolve与reject四个回调函数保存起来,等得知具体执行结果后再根据情况进行调用,如图中所示,值得注意的是,因为then方法可以进行多次调用,因此将handlers属性设置为一个数组会更为合适。此外,再额外定义一个run方法专门用于执行then方法中传递的回调,而在改变状态的changeState方法与then方法中调用这个run方法即可。
请添加图片描述

根据思路可以将代码完善如下

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

class MyPromise {

    #state = PENDING
    #result = undefined

    #handlers = []  // 定义一个参数handler数组,用来存储then方法中传入的回调函数

    // 定义一个run方法,专门用于根据不同情况,执行then方法中的不同的回调函数
    #run() {
        // 占位
    }

    constructor(executor) {
        const resolve = (data) => {
            this.#changeState(FULFILLED, data)
        }
        const reject = (reason) => {
            this.#changeState(REJECTED, reason)
        }
        try {
            executor(resolve, reject)
        } catch (error) {
            reject(error)
        }
    }
    #changeState(state, result) {
        if (this.#state !== PENDING) return
        this.#state = state
        this.#result = result
        this.#run()  // 执行定义好的run方法,以确保能执行then方法中异步传入结果的回调函数也能正常执行
    }

    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handlers.push({ // 向handlers数组中添加then方法中传入的每一组回调函数
                onFulfilled, onRejected, resolve, reject
            })
            this.#run()  // 直接执行
        })
    }
} 

实现了上述逻辑后,就该具体分析一下run方法内应该如何执行了,首先判断此时Promise的状态,如果还是pending,说明还没进行过状态变更,后续代码不用执行,直接返回即可;如果状态不再是pending,此时,对handlers进行循环遍历,依次取出handlers数组中的每一项,再根据当前Promise的状态来决定是执行onFulfilled还是onRejected,同时将Promise的结果作为函数的参数传入。

class MyPromise {

    ...
    // 定义一个run方法,专门用于根据不同情况,执行then方法中的不同的回调函数
    #run() {
        if (this.#state === PENDING) return
        while (this.#handler.length) {
            const { onFulfilled, onRejected, resolve, reject } = this.#handler.shift()
            if (this.#state === FULFILLED) {
                onFulfilled(this.#result)
            } else if (this.#state === REJECTED) {
                onRejected(this.#result)
            }
        }
    }
	...
} 

好了,先执行一下,看看结果如何,首先是成功的回调
1秒钟后成功输出,没有问题
再看一下失败的情况1秒钟后也成功输出,没有问题
成功和失败的回调均能正常输出,没有问题,接下来改判断then方法中返回的Promise的状态了。(解决上图中MyPromise状态一直是pending)

代码写到这里。其实出现了三种情况,分别是:

  1. 外界调用then方法时,传递的回调onFulfilled和onRejected不是函数
  2. 外界调用then方法时,传递的回调onFulfilled和onRejected不是函数

逐个情况分析一下,首先第一种情况,当外部调用时传递的不是一个函数,比如是个字符串或者null,或者干脆什么也不传,根据ES6实现的Promise来看,此时返回的Promise的状态应该和调用then方法的Promise状态和结果保持一致,(也被称为Promise的穿透性)根据这种情况,我们可以在实现时判断,如果onFulfilled或onRejected不是一个函数时,判断此时MyPromise的状态,如果是成功态,则直接执行resolve,将此时的结果作为参数传入;如果是失败态,也直接调用reject函数,将结果作为原因传入,实现代码如下。

class MyPromise {

    ...
    // 定义一个run方法,专门用于根据不同情况,执行then方法中的不同的回调函数
    #run() {
        if (this.#state === PENDING) return
        while (this.#handler.length) {
            const { onFulfilled, onRejected, resolve, reject } = this.#handler.shift()
            if (this.#state === FULFILLED) {
                if(typeof onFulfilled === 'function'){
                	onFulfilled(this.#result)
                }else{
					resolve(this.#result)
				}
            } else if (this.#state === REJECTED) {
                if(typeof onFulfilled === 'function'){
                	onRejected(this.#result)
                }else{
					reject(this.#result)
				}
            }
        }
    }

接下来就是第二种情况,即onFulfilled或onRejected是一个函数时,此时就需要运行这个函数,需要注意的是,此时应该进行判断,看onFulfilled或onRejected在运行过程中是否有报错,如果有报错,则应该执行reject,反正,正常运行则应该执行resolve。因此需要用try…catch包裹住执行函数,在try中正常执行函数,如果没有报错则把函数的返回结果保存下来,执行resolve并把这个返回结果作为resolve的参数传如;在catch中捕获报错,执行reject,并把错误原因作为参数传入reject。

// 定义一个run方法,专门用于根据不同情况,执行then方法中的不同的回调函数
#run() {
    if (this.#state === PENDING) return
    while (this.#handler.length) {
        const { onFulfilled, onRejected, resolve, reject } = this.#handler.shift()
        if (this.#state === FULFILLED) {
            if (typeof onFulfilled === 'function') {
                try {
                    const data = onFulfilled(this.#result)  // 判断回调函数执行是否报错
                    resolve(data)
                } catch (error) {
                    reject(error)
                }
            } else {
                resolve(this.#result)
            }
        } else if (this.#state === REJECTED) {
            if (typeof onFulfilled === 'function') {
                try {
                    const data = onRejected(this.#result)
                    resolve(data)
                } catch (error) {
                    reject(error)
                }
            } else {
                reject(this.#result)
            }
        }
    }
}

接下来就是最后一种情况了,也就是执行了onFulfilled或onRejected后返回的还是一个Promise,此时具体执行的是resolve还是reject就得看返回的Promise的结果到底是成功还是失败了。因此首先需要检测执行函数返回结果是不是一个Promise,这里定义一个isPromiseLike的辅助函数专门判断处理,当判断返回结果是一个Promise时,调用返回结果的then方法,把resolve和reject方法作为返回结果的then方法的两个回调传进去,如果返回结果的Promise成功,就会调用resolve,失败就会调用reject。

// 判断是否是符合Promise A+规范的promise
#isPromiseLike(value) {
    return false // 先作为占位,之后再实现
}

#run() {
    if (this.#state === PENDING) return
    while (this.#handler.length) {
        const { onFulfilled, onRejected, resolve, reject } = this.#handler.shift()
        if (this.#state === FULFILLED) {
            if (typeof onFulfilled === 'function') {
                try {
                    const data = onFulfilled(this.#result)
                    if (this.#isPromiseLike(data)) {  // 判断返回结果是否是一个Promise
                        data.then(resolve,reject)  // 是就执行返回结果的then方法
                    } else {
                        resolve(data)
                    }
                } catch (error) {
                    reject(error)
                }
            } else {
                resolve(this.#result)
            }
        } else if (this.#state === REJECTED) {
            if (typeof onFulfilled === 'function') {
                try {
                    const data = onRejected(this.#result)
                    if (this.#isPromiseLike(data)) {
                        data.then(resolve, reject)
                    } else {
                        resolve(data)
                    }
                } catch (error) {
                    reject(error)
                }
            } else {
                reject(this.#result)
            }
        }
    }
}

写到这里,我们会发现run方法中if与else if内的两中情况逻辑高度相似,完全可以抽离成一个单独的方法runOne,抽离后的代码如下。

#runOne(callback, resolve, reject) {  // callback 回调函数(onFulfilled||onRejected)
        if (typeof callback !== 'function') {  // 判断传入的回调函数是否是函数,不是则走穿透逻辑
            const settled = this.#state === FULFILLED ? resolve : reject // 定义一个变量settled,根据当前的state判断此次应该执行什么函数
            settled(this.#result)
            return;  // 确定不是函数,后面的逻辑也不会再走了
        }
        try {  
            const data = callback(this.#result)
            if (this.#isPromiseLike(data)) { // 回调函数正常执行且回调函数的返回结果是个Promise,需要调用返回结果的then方法,并把resolve和reject传进去,由返回结果的Promise判断应该执行哪个
                data.then(resolve, reject)
            } else {
                resolve(data)  // 回调函数正常执行且回调函数的返回结果不是个Promise,执行resolve
            }
        } catch (error) { 
            reject(error)   // 捕获到了回调函数执行时抛出的错误,此时无论state是什么都会执行reject
        }
    }

    #run() {
        if (this.#state === PENDING) return
        while (this.#handler.length) {
            const { onFulfilled, onRejected, resolve, reject } = this.#handler.shift()
            if (this.#state === FULFILLED) {
                this.#runOne(onFulfilled, resolve, reject) // 成功传递onFulfilled
            } else if (this.#state === REJECTED) {
                this.#runOne(onRejected, resolve, reject)// 失败传递onRejected
            }
        }
    }

逻辑抽离完成了,接下来改完善之前定义的用于判断是否是Promise的辅助函数。
根据Promise A+规范,不管是对象还是函数,只要它包含了一个叫做then的函数的属性,都可以叫Promise,因此判断方法也很简单

#isPromiseLike(value){
    if (value && (typeof value === 'object' || typeof value === 'function')) {
        return typeof value.then==='function'
    }
    return false
}

PS:为什么这里需要这个辅助函数而不是直接采用 data instanceof MyPromise进行判断呢?
答案:因为判断一个东西是否是Promise,应该是看它是不是满足Promise A+规范,Promise之间应该具有互操作性,比如ES6的Promise和我们创建的MyPromise直接就应该可以互相操作,但如果直接用 data instanceof MyPromise判断一个由ES6创建的Promise时,这个结果肯定是false,反之,如果用 data instanceof MyPromise判断一个由MyPromise创建的Promise时,结果也肯定是false,但实际上这两种情况都应该返回true才对,因此用instanceof判断无法满足这里的需要。

功能大致实现了,但还有一点需要注意,运行then方法中的回调应该是放在微队列里的,因此还需要一个辅助函数实现将方法放入微队列的功能,这里定义一个runMicroTask,具体实现逻辑参考vue源码,分成三种情况

  • node环境:用process.nextTick模拟微队列
  • 浏览器:用MutationObserver模拟微队列
  • 既不在浏览器也不在node环境,此时没办法了,因为微队列是环境提供的能力,脱离环境JS是无法实现的,因此只能用setTimeout作为替代
// 将任务放入异步队列里
    #runMicroTask(func) {
        if (typeof process === 'object' && typeof process.nextTick === 'function') {  // 模拟node环境微队列执行
            process.nextTick(func)
        } else if (typeof MutationObserver === 'function') {  // 模拟浏览器环境的微队列执行
            const ob = new MutationObserver(func)  // MutationObserver是一个观察器,它会自动将一个回调函数放入微队列
            const textNode = document.createTextNode('1')  // 构建一个文本节点用于观察器观察
            ob.observe(textNode, {  // 指定观察器观察的节点
                characterData: true // 观察字符的变化
            })
            textNode.data='2'  // 手动修改文本节点,当被观察的对象发生变化时,观察器会把回调放入微队列执行,从而实现模拟微队列
        } else {
            setTimeout(func, 0)  // 既不是node环境也不是浏览器,只能用setTimeout来实现异步了
        }
    }

	... 
	
    #runOne(callback, resolve, reject) { 
        this.#runMicroTask(() => {  // 将之前的逻辑通过一个箭头函数的形式放入runMicroTask实现模拟放入微队列
            if (typeof callback !== 'function') {  
                const settled = this.#state === FULFILLED ? resolve : reject 
                settled(this.#result)
                return; 
            }
            try {
                const data = callback(this.#result)
                if (this.#isPromiseLike(data)) { 
                    data.then(resolve, reject)
                } else {
                    resolve(data)  
                }
            } catch (error) {
                reject(error)   
            }
        })
    }
	...


ok,全部写完,找几个结果测试一下
请添加图片描述
成功在1秒后输出,状态也发生了改变

请添加图片描述
请添加图片描述

输出顺序也没有问题。

至此,基于Promise A+规范的MyPromise代码全部完成。Promise的其余方法,如catch,finally,resolve,reject,all等方法,这些都是ES6中额外加入的,Promise A+规范中并未对这些方法的实现有提及,因此这些方法的实现放在下一篇文章中。


总结

本篇文章,着重对MyPromise中的then方法的执行时机和返回结果进行了实现,并完成了检测是否是Promise和实现微队列效果的两个辅助函数。

最后,附上MyPromise的完整代码

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

class MyPromise {

    #state = PENDING
    #result = undefined

    #handler = []

    // 判断是否是符合Promise A+规范的promise
    #isPromiseLike(value) {
        if (value && (typeof value === 'object' || typeof value === 'function')) {
            return typeof value.then === 'function'
        }
        return false
    }

    // 将任务放入异步队列里
    #runMicroTask(func) {
        if (typeof process === 'object' && typeof process.nextTick === 'function') {  // 模拟node环境微队列执行
            process.nextTick(func)
        } else if (typeof MutationObserver === 'function') {  // 模拟浏览器环境的微队列执行
            const ob = new MutationObserver(func)  // MutationObserver是一个观察器,它会自动将一个回调函数放入微队列
            const textNode = document.createTextNode('1')  // 构建一个文本节点用于观察器观察
            ob.observe(textNode, {  // 指定观察器观察的节点
                characterData: true // 观察字符的变化
            })
            textNode.data='2'  // 手动修改文本节点,当被观察的对象发生变化时,观察器会把回调放入微队列执行,从而实现模拟微队列
        } else {
            setTimeout(func, 0)  // 既不是node环境也不是浏览器,只能用setTimeout来实现异步了
        }
    }

    #runOne(callback, resolve, reject) {  // callback 回调函数(onFulfilled||onRejected)
        this.#runMicroTask(() => {  // 将之前的逻辑通过一个箭头函数的形式放入runMicroTask实现模拟放入微队列
            if (typeof callback !== 'function') {  // 判断传入的回调函数是否是函数,不是则走穿透逻辑
                const settled = this.#state === FULFILLED ? resolve : reject // 定义一个变量settled,根据当前的state判断此次应该执行什么函数
                settled(this.#result)
                return;  // 确定不是函数,后面的逻辑也不会再走了
            }
            try {
                const data = callback(this.#result)
                if (this.#isPromiseLike(data)) { // 回调函数正常执行且回调函数的返回结果是个Promise,需要调用返回结果的then方法,并把resolve和reject传进去,由返回结果的Promise判断应该执行哪个
                    data.then(resolve, reject)
                } else {
                    resolve(data)  // 回调函数正常执行且回调函数的返回结果不是个Promise,执行resolve
                }
            } catch (error) {
                reject(error)   // 捕获到了回调函数执行时抛出的错误,此时无论state是什么都会执行reject
            }
        })
    }

    #run() {
        if (this.#state === PENDING) return
        while (this.#handler.length) {
            const { onFulfilled, onRejected, resolve, reject } = this.#handler.shift()
            if (this.#state === FULFILLED) {
                this.#runOne(onFulfilled, resolve, reject) // 成功传递onFulfilled
            } else if (this.#state === REJECTED) {
                this.#runOne(onRejected, resolve, reject)// 失败传递onRejected
            }
        }
    }

    constructor(executor) {
        const resolve = (data) => {
            this.#changeState(FULFILLED, data)
        }
        const reject = (reason) => {
            this.#changeState(REJECTED, reason)
        }
        try {
            executor(resolve, reject)
        } catch (error) {
            reject(error)
        }
    }
    #changeState(state, result) {
        if (this.#state !== PENDING) return
        this.#state = state
        this.#result = result
        this.#run()
    }

    then(onFulfilled, onRejected) {
        return new MyPromise((resolve, reject) => {
            this.#handler.push({
                onFulfilled, onRejected, resolve, reject
            })
            this.#run()
        })
    }
}
  • 22
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值