状态分解
首先对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微任务还不够完美...
原文链接:https://juejin.cn/post/7375072812847005706