likes
comments
collection
share

面试官:请手写实现一个符合规范的简易 Promise

作者站长头像
站长
· 阅读数 26

引言

JavaScript 的 Promise 是一种用于处理异步操作的模式,它比传统的回调更加优雅和强大。在面试中也是比较高频的考点。为了更好地理解和掌握 Promise 的内部机制,我们需要尝试自己实现一个简易版本的 Promise 类,命名为 MyPromise

分析与实现---基础实现

function a() {
    return new Promise((reslove, reject) => {
        setTimeout(() => {
            console.log('a')
            reslove(1)
        }, 1000)
        // console.log('a')
        // reslove(1)
    })
}
function b(){
    console.log('b')
}
function c(){
    console.log('c')
}
// a()
// .then(b)
// .then(c)
// .catch(error => {
//   console.log(error)
// })

a()
.then((res) => {
    b()
    console.log(res)
    return res
})
.then((res) => {
    c()
    console.log(res)
})
.catch((error) => {
    console.log(error)
})

我们来分析一下上面的Promise有哪些功能和细节。

我们可以看到new Promise中传递了一个函数作为参数,所以我们可以选择用去模拟实现Promise,类中的constructor函数可以接受一个参数进行实例化对象。接受参数后,马上执行。于是我们写出如下:

class MyPromise {
    constructor(executor){
        executor()
    }
}

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

executor参数是一个函数,这个函数接收两个参数reslovereject,这两能够传参和调用,因此constructor中还需要两个变量且值为函数体,如下:

class MyPromise {
    constructor(executor){
        const resolve = () => {}
        const reject = () => {}
        executor(resolve, reject)
    }
}

调用a函数后会返回一个promise实例对象,而这个实例对象后面可以接.then方法,因此可以判断Promise 类中会有一个.then方法,接受一个回调函数作为参数,而.then方法需要做一件什么事呢?你可能会产生一个误区,认为.then方法使得接受的回调函数执行,其实不然。当你不写reslove()reject()时,回调函数不会触发执行,因此.then方法里的回调函数执不执行完全由reslove()reject()说的算。那么.then方法到底干了件什么事呢?

正如所说,回调函数是由reslove()reject()触发的,因此,回调函数肯定在reslove()reject()函数体里,不然怎么去触发,所以.then方法的一个主要功能就是将回调函数存储在一个容器中,然后再把这个容器放入reslove()reject()调用,因此我们可以在构造函数里两个容器,如下:

class MyPromise {
    constructor(executor){
        this.onFulfilledCallbacks = []  // 成功的回调函数(.then有多个, 所以有多个回调函数,用数组存储)
        this.onRejectedCallbacks = []  // 失败的回调函数
        const resolve = () => {}
        const reject = () => {}
        executor(resolve, reject)
    }
    then(){
    }
}

我们又想到reslove()reject()是对立的,要么有我没他,要么没他有我,总之一句话,状态一旦发生改变,就不可更改,所以我们需要存储状态的变量并在函数中进行状态判断,如下:

class MyPromise {
    constructor(executor){
        this.state = 'pending'
        this.onFulfilledCallbacks = []  // 成功的回调函数(.then有多个, 所以有多个回调函数,用数组存储)
        this.onRejectedCallbacks = []  // 失败的回调函数
        const resolve = () => {
            if(this.state === 'pending'){
                this.state = 'fulfilled'
            }
        }
        const reject = () => {
            if(this.state === 'pending'){
                this.state = 'rejected'
            }
        }
        executor(resolve, reject)
    }
    then(){
    }
}

我们还想到reslove()reject()会接受一个参数,这个参数就是.then()中的回调函数上的参数,所以我们应该将reslove()reject()上的参数记录下来以便传递给.then方法,于是我们又这样写:

class MyPromise {
    constructor(executor){
        this.state = 'pending'
        this.value = undefined  // 记录resolve的参数
        this.reason = undefined  // 记录reject的参数
        this.onFulfilledCallbacks = []  // 成功的回调函数(.then有多个, 所以有多个回调函数,用数组存储)
        this.onRejectedCallbacks = []  // 失败的回调函数
        const resolve = (value) => {
            if(this.state === 'pending'){
                this.state = 'fulfilled'
                this.value = value
                this.onFulfilledCallbacks.forEach(cb => cb(value))  // 顺便把这步补上
            }
        }
        const reject = () => {
            if(this.state === 'pending'){
                this.state = 'rejected'
                this.reason = reason
                this.onRejectedCallbacks.forEach(cb => cb(reason))  // 顺便把这步补上
            }
        }
        executor(resolve, reject)
    }
    then(){
    }
}

三、分析与实现---.then方法实现

现在就剩then()函数的实现了,我们之前分析过,他的主要功能是存储回调函数,但还有其他的功能和细节,我们来分析一下:

Promise后面可以接着写多个then方法,所以要想写两个以上的then方法,那么then函数里肯定是需要返回一个Promise

另外一个方面,then函数中需要传两个参数即成功的回调函数失败的回调函数,在这里要注意一下,如果传入的参数是非函数体,那么就需要做出响应,因此就需要判断,如下:

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

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

.then做出的动作因状态而改变,有三种状态,就有三种情况,我们分别进行分析:

1. fulfilled状态

当Promise中的内容为非异步任务时,那么意味着任务会马上执行,那么resolve马上会执行,state状态也会变更为fulfilled,而此时.then还没有将回调函数给resolve,因此回调函数并没触发,但真实的情况是回调函数还是会触发,鉴于这种情况,我们需要做出如下改变:

state状态为fulfilled时,直接触发回调函数执行,如果回调函数有返回值给下一个.then,就需要传参。

if(this.state === 'fulfilled'){
    const res = onFulfilled(this.value)
    resolve(res)
}

这里有个细节,.then是微任务,所以我们需要把它变为微任务,但实现起来过于复杂,所以我用宏任务setTimeout去模拟微任务......,另外顺便用try处理一下意外错误:

if(this.state === 'fulfilled'){
    setTimeout( () => {  // 模拟异步执行,但是模拟不了微任务
        try{
            const res = onFulfilled(this.value)
            resolve(res)
        }catch(error){
            reject(error)
        }
    })
}

2. rejected状态

逻辑和上面的一样,如下代码:

if(this.state === 'rejected'){
    setTimeout( () => {
        try{
            const res = onRejected(this.reason)
            resolve(res)
        }catch(error){
            reject(error)
        }
    })
}

3. pending状态

这种状态就是正常状态了,将回调函数放入容器中,顺便模拟为微任务如下:

if(this.state === 'pending'){
    // 将自己的回调存进onFulfilledCallbacks和onRejectedCallbacks中
    this.onFulfilledCallbacks.push((value) => {
        setTimeout(() => {
            onFulfilled(value)
        })
    })

    this.onRejectedCallbacks.push((reason) => {
        setTimeout(() => {
            onRejected(reason)
        })
    })
}

四、附上整体实现代码:

class MyPromise{
    constructor(executor){
        this.status = 'pending'
        this.value = undefined  // 临时保存 resolve 的参数
        this.reason = undefined  // 临时保存 reject 的参数
        this.onFulfilledCallbacks = []  // 成功的回调函数
        this.onRejectedCallbacks = []  // 失败的回调函数
        const resolve = (value) => {
            if(this.status === 'pending'){
                this.status = 'fulfilled'
                this.value = value
                this.onFulfilledCallbacks.forEach(cb => cb(value))
            }
        }
        const reject = (reason) => {
            if(this.status === 'pending'){
                this.status = 'rejected'
                this.reason = reason
                this.onRejectedCallbacks.forEach(cb => cb(reason))
            }
        }
        executor(resolve, reject)
    }
    
    then (onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {throw reason}

        return new MyPromise((resolve, reject) => {
            if(this.state === 'fulfilled'){
                setTimeout( () => {  // 模拟异步执行,但是模拟不了微任务
                    try{
                        const res = onFulfilled(this.value)
                        resolve(res)
                    }catch(error){
                        reject(error)
                    }
                    
                })
            }
            if(this.state === 'rejected'){
                setTimeout( () => {
                    try{
                        const res = onRejected(this.reason)
                        resolve(res)
                    }catch(error){
                        reject(error)
                    }
                })
            }
            if(this.state === 'pending'){
                // 将自己的回调存进onFulfilledCallbacks和onRejectedCallbacks中
                this.onFulfilledCallbacks.push((value) => {
                    setTimeout(() => {
                        onFulfilled(value)
                    })
                })

                this.onRejectedCallbacks.push((reason) => {
                    setTimeout(() => {
                        onRejected(reason)
                    })
                })
            }
        })
    }
}

五、结语

写的不好,理解起来会有点绕,但只要多加理解,肯定是没问题,面试中关于Promise的话题是永久不息的,希望你我能克服难题,技术更上一层楼,感谢阅读!!!

转载自:https://juejin.cn/post/7392848248488067087
评论
请登录