面试官:"promise有了解吗?请手动实现一下..."。此时面试官就可以去可以心安理得的去倒茶了。
首先在面试的时候需要冷静处理,即使遇到不会的,先思考,后下笔。把想到的思路用代码表示出来即可,不要求写出多么标准正确的答案,目的是让面试官看到你是有思路。
状态分解
首先对promise进行分解
- 状态机:3种状态
- 状态不可逆
前期我们只需要先大概知道这几个特性,然后想办法用代码实现。
js
class myPromise {
constructor(executor) {
this.status = 'pending'
this.res = null
executor(this.resolve, this.reject)
}
resolve(val) {
if(this.status !== 'pending') return
this.status = 'fulfilled'
this.res = val
}
reject(val) {
if(this.status !== 'pending') return
this.status = 'rejected'
this.res = val
}
}
其实写到这里很多面试官是认可的,后面写不出来的话面试官会进行提示。堂堂手写promise在前端八股地位还是很高的,可不是这么简简单单敷衍了事。
this指向问题
上述代码其实是有问题的,兄台请看:
js
new myPromise((resolve, reject) => {
resolve(100)
})
因为 resolve(100)
函数执行的位置在全局作用域,全局作用域中没有this,因此无法找到status。所以这个函数需要进行this绑定到promise实例。该函数是从如下图片显示的位置作为参数传入的,所以需要在这里进行this绑定。
this绑定后
then回调
执行 resolve/reject
函数需要进行then回调,也就是状态发生变化的时候执行then回调中的函数,我们基于这个思路继续完善promise。
js
...
resolve() {...}
reject() {...}
then(resolveCb, rejectCb) {
if(this.status === 'fulfilled') resolveCb(this.res)
if(this.status === 'rejected') rejectCb(this.res)
}
定时器问题
这样写会有一个问题,当new myPromise时,传入一个定时器,在定时中在进行resolve/reject
时then回调会出现问题。因为定时器是一个异步任务状态会延迟更新 ,而执行then函数时myPromise的状态还是 pending
,此时then的res拿到的是undefined。
解决方案 :用一个队列来维护定时器resolve/reject
的then回调,当then回调检测到状态没有发生改变则将回调函数push到各种的队列中,状态发生改变后在从队列中取出回调函数执行。
一、定义回调队列
二、then回调,若为初始状态则push到回调队列
三、定时器结束后执行函数时从队列中取出回调函数执行
链式调用
then回调是支持链式调用的,也就是then.then.then,并且由api特性可知then是return一个promise的,并且这个promise的res是上一个promise的resolve/rejecte
后的res,我们基于这个思路继续完善then回调。
js
then(resolveCb, rejectCb) {
return new Promise((resolve, reject) => {
// 处理then中的resolve和reject回调
const handleThenCb = (thenCb) => {
try {
const res = thenCb(this.res)
// 若返回的结果是promise的话需要等待这个promise状态的改变
// promise状态会在哪里发生改变?在promise.then中
// 所以把resolve和rejecte作为then回调,当状态改变后执行reslove/reject
if(res instanceof myPromise) {
res.then(resolve, reject)
} else {
// 返回结果不是promise则直接reslove
resolve(res)
}
} catch (error) {
// 发生异常则reject
reject(error)
}
}
if(this.status === 'fulfilled') handleThenCb(resolveCb)
if(this.status === 'rejected') handleThenCb(rejectCb)
if(this.status === 'pending') {
this.resolveCbQueue.push(handleThenCb(resolveCb))
this.rejectCbQueue.push(handleThenCb(rejectCb))
}
})
}
当你试图运行时会发生错误,仔细看这一步,我们之前的想法是让这个定时器回调push到队列中,但如果这样写的话相当于将回调执行后的返回值push到队列中,仔细想想是不是这样呢!
解决方案 :使用 bind
函数传入上下文和对应参数,结果是将bind返回的这个新函数push到队列中,这个新函数其实还是执行原来的 handleThenCb
并且this指向也正确从而完美解决。
且慢,你是不是忘了then回调是一个微任务,使用了回调队列解决定时器,这里在给then回调封装一层定时器,达到微任务的效果。
合并效果
js
class myPromise {
constructor(executor) {
this.status = 'pending'
this.res = null
this.resolveCbQueue = []
this.rejectCbQueue = []
executor(this.resolve.bind(this), this.reject.bind(this))
}
resolve(val) {
if(this.status !== 'pending') return
this.status = 'fulfilled'
this.res = val
while(this.resolveCbQueue.length) {
this.resolveCbQueue.shift()(this.res)
}
}
reject(val) {
if(this.status !== 'pending') return
this.status = 'rejected'
this.res = val
while(this.resolveCbQueue.length) {
this.resolveCbQueue.shift()(this.res)
}
}
then(resolveCb, rejectCb) {
return new Promise((resolve, reject) => {
const handleThenCb = (thenCb) => {
try {
const res = thenCb(this.res)
if(res instanceof myPromise) {
res.then(resolve, reject)
} else {
resolve(res)
}
} catch (error) {
reject(error)
}
}
if(this.status === 'fulfilled') setTimeout(() => handleThenCb(resolveCb))
if(this.status === 'rejected') setTimeout(() => handleThenCb(rejectCb))
if(this.status === 'pending') {
this.resolveCbQueue.push(handleThenCb.bind(this, resolveCb))
this.rejectCbQueue.push(handleThenCb.bind(this, rejectCb))
}
})
}
}
最后将上述代码思路进行合并看看效果。
彩蛋:当setTimeout 的延迟时间为0时,then微任务还不够完美...