【面试题】吃透Promise?先实现一个再说(包含所有方法)_this,五面拿下阿里飞猪offer

1.1 前景概要 :

首先要了解promiseA+规范只是社区对于开发者实现promise提出的一个合理的规范而已,它与ECMA规范下的promise(浏览器下的)有很多区别,但是可以满足大部分工作以及学习需求,而且理解比较简单,我们就来简单实现一个promise以及梳理它的流程

let p1 = new Promise((resolve, reject) => {
    resolve('成功')
})

复制代码

1.2 Promise是构造函数

这是promise的起点,通过形式可以看到 promise属于构造函数 我们需要通过new关键字来调用,内部接收一个回调函数(我们采用executor代理),内部有2个参数resolve,reject分别是2个回调函数,各携带一个参数,所以我们的雏形来了

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

复制代码

继续通过一段代码了解promise

1.3 Promise的状态变更

let p1 = new Promise((resolve, reject) => {
    resolve('成功')
    reject('失败')
})
p1.then(
    (value) => {
        console.log(value) //成功
    },
    (err) => {
        console.log(err)
    }
)
let p2 = new Promise((resolve, reject) => {
    reject('失败')
    resolve('成功')
})
p2.then(
    (value) => {
        console.log(value)
    },
    (err) => {
        console.log(err) //失败
    }
)
复制代码

通过上述代码可以知道Promise返回一个实例,并且实例带有then方法,且then方法中包含2个回调函数,(我们以onFulfilled和onRejected代替)可以通过回调函数的参数获取,我们可以通过resolve 和reject函数传递结果,并且通过then里面的回调函数接收对应的结果,而且promise会通过resolve,reject确定状态,一旦确定好状态,就只执行对应的回调函数,忽略其他的resolve或者reject

因此我们需要来指定状态,并且存储resolve,reject的值,从而传递给then

const [PENDING, FULFILLED, REJECTED] = ['PENDING', 'FULFILLED', 'REJECTED']
class MyPromise {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
            }
        }
        executor(resolve, reject)
    }
    
    then(onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value)
        }

        if (this.status === REJECTED) {
            onRejected(this.reaosn)
        }
    }
}

复制代码

上述代码,我们声明了PENDING(等待),FULFILLED(成功),REJECTED(失败)三个状态,来记录promise的状态,并且当resolve或者reject时,我们立即修改状态,并且将成功或者失败的值存储起来。在then的回调函数中通过状态判断来执行对应的回调函数

1.4 解决异步

但是promise是用来解决异步问题的,我们的代码全部是同步执行,还有很多缺陷,例如:

const p1 = new Promise((resolve, reject) => {
        setTimeout(function () {
            resolve('成功')
        })
    },3000)
    p1.then((value) => {
        console.log(value)
    })
复制代码

正常情况下会等待3秒确定状态,然后执行对应then的回调函数,但是我们的代码却不会执行,因为刚才也说过我们的代码全部都是同步执行,没有对PENDING状态进行处理,因此我们需要额外对pending状态进行处理 代码如下

const [PENDING, FULFILLED, REJECTED] = ['PENDING', 'FULFILLED', 'REJECTED']
class MyPromise {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
                this.onFulfilled && this.onFulfilled(value)
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejected && this.onRejected(reason)
            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            onFulfilled(this.value)
        }

        if (this.status === REJECTED) {
            onRejected(this.reaosn)
        }
        if (this.status === PENDING) {
            // 存储回调函数
            this.onFulfilled = onFulfilled 
            this.onRejected = onRejected
        }
    }
}

复制代码

我们在pending状态下将then的回调函数存储下来,在status改变状态后立即执行达到支持异步的效果

1.5 then的微任务?

我们在通过一个例子来完善代码

let p1 = new Promise((resolve, reject) => {
            resolve('成功')
        })
p1.then((value) => console.log(value))
console.log(11)
// 11 => 成功
复制代码

通过上述代码,以及promise的知识我们应该知道then的回调函数实际是将这个回调加入到了微任务队列中 所以先打印11 然后再打印成功,而我们的代码却并是同步执行,我们需要将then的回调函数模拟微任务的形式,这里我们使用setTimeout来模拟微任务,修改我们的代码

then(onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.value)
            })
        }

        if (this.status === REJECTED) {
            setTimeout(() => {
                onRejected(this.reason)
            })
        }
        if (this.status === PENDING) {
            this.onFulfilled = onFulfilled
            this.onRejected = onRejected
        }
    }
复制代码

1.6存储处理函数的数据结构

这样就可以解决上述then的回调要进入微任务的情况, 下一个我们要解决的问题是promise多次调用的问题

let p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve('成功')
    })
})
p1.then((value) => {
    console.log(value,111)
})
p1.then((value) => {
    console.log(value,222)
})
复制代码

上述代码会依次打印成功,但是我们的代码不具备这种条件因为我们的then方法中的onFulfilled会覆盖第一个then的方法的OnFulfilled 这个问题也比较好解决,我们只需要通过一个数组将函数存储起来,到时候遍历调用即可 此时完整代码如下

const [PENDING, FULFILLED, REJECTED] = ['PENDING', 'FULFILLED', 'REJECTED']
class MyPromise {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
        const resolve = (value) => {
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
                this.onFulfilledCallbacks.forEach((fn) => fn())
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejectedCallbacks.forEach((fn) => fn())
            }
        }
        executor(resolve, reject)
    }
    then(onFulfilled, onRejected) {
        if (this.status === FULFILLED) {
            setTimeout(() => {
                onFulfilled(this.value)
            }, 0)
        }

        if (this.status === REJECTED) {
            setTimeout(() => {
                onRejected(this.reason)
            }, 0)
        }
        if (this.status === PENDING) {
            this.onFulfilledCallbacks.push(() => {
                onFulfilled(this.value)
            })
            this.onRejectedCallbacks.push(() => {
                onRejected(this.reason)
            })
        }
    }
}

复制代码

接下来我们放松一下,处理点小问题,

1.7 trycatch的引用

let p1 = new MyPromise((resolve, reject) => {
    throw new Error('我要报错')
})
p1.then(
    (value) => {
        console.log(value, 111)
    },
    (err) => {
        console.log(err)
    }
)
复制代码

在executor函数中如果报错,如果我们指定了then方法的接收函数的话,promise将其定义为REJECTED状态, 那我们只需要简单的try/catch进行处理下,遇到错误直接reject就完事了(其实其中大有文章,末尾发链接

try {
    executor(resolve, reject)
} catch (e) {
    reject(e)
}
复制代码

好了放松完了,我们来点刺激的

2.Promise的链式调用

promise的核心以及最大特点就是链式调用,比如then回调函数的返回值会包裹成一个promise

promiseA+规范 2.27明确说明then方法必须返回一个promsie,并且onfulfilled或者onRejected返回值需要再次进行处理(the Promise Resolution Procedure),如果出现异常我们需要reject出去

then(onFulfilled, onRejected) {
    let promise2 = new MyPromise((resolve, reject) => {
        if (this.status === FULFILLED) {
            setTimeout(() => {
                try {
                    let x = onFulfilled(this.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            }, 0)
        }

        if (this.status === REJECTED) {
            setTimeout(() => {
                try {
                    let x = onRejected(this.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            }, 0)
        }
        if (this.status === PENDING) {
            this.onFulfilledCallbacks.push(() => {
                try {
                    let x = onFulfilled(this.value)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
            this.onRejectedCallbacks.push(() => {
                try {
                    let x = onRejected(this.reason)
                    resolvePromise(promise2, x, resolve, reject)
                } catch (e) {
                    reject(e)
                }
            })
        }
    })
    return promise2
}
复制代码

我们按照规范分别对onFulfilled以及OnRejected的函数返回值做了处理(ResolvePromise后面再提作用) 也做错了异常检测 2.27.3 与2.27.4是对then的穿透做处理比较简单 如果onfulfiled不是一个函数并且这个promise的状态是fulfilled,返回值promise2必须指定为一个fulfilled的函数并返回上一个then返回的相同的值 如果onrejected不是一个函数并且这个promsie的状态是rejected,我们只需要将这个rejected的的错误继续抛出即可

 onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
 onRejected = typeof onRejected ==='function' ? onRejected :reason=>{throw reason}
复制代码

这样就简单实现了then的穿透,但是只能验证rejected的情况,需要将resolvePromise函数完成才能达到效果

2.1对then的返回值的封装(resolvePromsie)

我们直接从规范2.3.1开始

如果这个promise与返回值x相等,则需要reject这个类型错误 类似于这种情况:

let p1 = new Promise((resolve, reject) => {
    resolve('成功')
}).then((value) => {
    return p1
    // Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise>
})
复制代码

那么我们开始封装resolvePromise

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
}
复制代码

2.3.2是针对x如果是一个promise对象

需要通过对PENDING,FULFILLED,REJECTED3个状态进行 如果x处于pending状态,那么在成功或者失败前,我们需要保存这个状态, 如果x处于fulfilled或者rejected状态我们只需要重新resove或者reject出去即可

2.3.3如果x一个对象或者函数 如果x不是一个对象或者函数,那么为普通值我们直接resolve出去

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
    } else {
        resolve(x)
    }
}
复制代码

初步的模型已经完成,此时关于前面then的穿透问题可以大致看出来已经解决

  let p1 = new MyPromise((resolve, reject) => {
                resolve('成功')
            })
    .then()
    .then()
    .then(
        (value) => {
            console.log(value) // 成功
        },
        (err) => {
            console.log(err)
        }
    )
复制代码

接下来继续按照规范来,从2.3.1处开始处理对象或者函数的情况 2.3.1说假设x有一个then属性, 2.3.2:在读取属性的时候如果抛出异常则reject出去(Object.defineProerty(x,‘then’,{get(){throw new Error(‘err’)}})) 则代码如下

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let then = x.then
        try {
        } catch (e) {
            reject(e)
        }
    } else {
        resolve(x)
    }
}
复制代码

2.3.3开始对then进行处理,如果then是一个函数则认为x是一个promise对象,然后调用它(
If then is a function, call it with x as this, first argument resolvePromise, and second argument rejectPromise, where:)并且附带2个参数(函数)处理resolve(参数y)和reject(参数r)这里指的是2.3.3.3.1和2.3.3.3.2 2.3.3.3的意思是如果r和y被多次调用或者对某个函数重复调用,第一次调用优先,其他忽略,因此我们指定一个全局变量called来控制调用

2.3.3.4的意思是如果调用后抛出异常,这个异常可能在调用y或者r函数后造成也可能是在之前就抛出的 因此也需要使用called来控制是否抛出异常 2.3.4以及后面的指的是如果then不是一个函数或者对象,那么确定fulfilled状态resolve出去即可 至此完整resolvePromise函数封装如下

function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    let called = false
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let then = x.then
        try {
            if (typeof then === 'function') {
                then.call(
                    x,
                    (y) => {
                        if (called) return
                        called = true
                        resolvePromise(promise2, y, resolve, reject)
                    },
                    (r) => {
                        if (called) return
                        called = true
                        reject(r)
                    }
                )
            } else {
                resolve(x)
            }
        } catch (e) {
            if (called) return
            called = true
            reject(e)
        }
    } else {
        resolve(x)
    }
}

复制代码

对了突然想起来对于executor函数中的resolve封装中,如果resolve里面是多层嵌套的promsie对象的话例如这样

let p1 = new Promise((resolve, reject) => {
        resolve(
            new Promise((resolve, reject) => {
                resolve(11)
            })
        )
    }).then((value) => {
        console.log(value)
    })
复制代码

我们需要对resolve的参数做一个提前的判断处理,如果是promsie的实例我们应该调用then方法 添加起来非常简单,代码如下

 const resolve = (value) => {
            if (value instanceof MyPromise) {
                value.then(resolve, reject)
                return
            }
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
                this.onFulfilledCallbacks.forEach((fn) => fn())
            }
        }
复制代码

至此完整的promise实现代码如下

const [PENDING, FULFILLED, REJECTED] = ['PENDING', 'FULFILLED', 'REJECTED']
class MyPromise {
    constructor(executor) {
        this.status = PENDING
        this.value = undefined
        this.reason = undefined
        this.onFulfilledCallbacks = []
        this.onRejectedCallbacks = []
        const resolve = (value) => {
            if (value instanceof MyPromise) {
                value.then(resolve, reject)
                return
            }
            if (this.status === PENDING) {
                this.status = FULFILLED
                this.value = value
                this.onFulfilledCallbacks.forEach((fn) => fn())
            }
        }
        const reject = (reason) => {
            if (this.status === PENDING) {
                this.status = REJECTED
                this.reason = reason
                this.onRejectedCallbacks.forEach((fn) => fn())
            }
        }
        try {
            executor(resolve, reject)
        } catch (e) {
            reject(e)
        }
    }
    then(onFulfilled, onRejected) {
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : (value) => value
        onRejected =
            typeof onRejected === 'function'
                ? onRejected
                : (reason) => {
                      throw reason
                  }
        let promise2 = new MyPromise((resolve, reject) => {
            if (this.status === FULFILLED) {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            }

            if (this.status === REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason)
                        resolvePromise(promise2, x, resolve, reject)
                    } catch (e) {
                        reject(e)
                    }
                }, 0)
            }
            if (this.status === PENDING) {
                this.onFulfilledCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    })
                })
                this.onRejectedCallbacks.push(() => {
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason)
                            resolvePromise(promise2, x, resolve, reject)
                        } catch (e) {
                            reject(e)
                        }
                    })
                })
            }
        })
        return promise2
    }    
}
function resolvePromise(promise2, x, resolve, reject) {
    if (x === promise2) {
        return reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    let called = false
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let then = x.then
        try {
            if (typeof then === 'function') {
                then.call(
                    x,
                    (y) => {
                        if (called) return
                        called = true
                        resolvePromise(promise2, y, resolve, reject)
                    },
                    (r) => {


### 结尾

正式学习前端大概 3 年多了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的一些书,虽然整理的书不多,但是每一本都是那种看一本就秒不绝口的感觉。

以下大部分是我看过的,或者说身边的人推荐的书籍,每一本我都有些相关的推荐语,如果你有看到更好的书欢迎推荐呀。



eturn reject(new TypeError('Chaining cycle detected for promise #<MyPromise>'))
    }
    let called = false
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        let then = x.then
        try {
            if (typeof then === 'function') {
                then.call(
                    x,
                    (y) => {
                        if (called) return
                        called = true
                        resolvePromise(promise2, y, resolve, reject)
                    },
                    (r) => {


### 结尾

正式学习前端大概 3 年多了,很早就想整理这个书单了,因为常常会有朋友问,前端该如何学习,学习前端该看哪些书,我就讲讲我学习的道路中看的一些书,虽然整理的书不多,但是每一本都是那种看一本就秒不绝口的感觉。

以下大部分是我看过的,或者说身边的人推荐的书籍,每一本我都有些相关的推荐语,如果你有看到更好的书欢迎推荐呀。



![前端学习书籍导图-1](https://img-blog.csdnimg.cn/img_convert/6a36760ef739055f4ec668b19d21ee89.webp?x-oss-process=image/format,png)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值