Bootstrap

手写promise-面试版

状态分解

首先对promise进行分解

  1. 状态机:3种状态
  2. 状态不可逆

前期我们只需要先大概知道这几个特性,然后想办法用代码实现。

 

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) })

image.png

因为 resolve(100) 函数执行的位置在全局作用域,全局作用域中没有this,因此无法找到status。所以这个函数需要进行this绑定到promise实例。该函数是从如下图片显示的位置作为参数传入的,所以需要在这里进行this绑定。

image.png

this绑定后

image.png

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。

image.png

解决方案:用一个队列来维护定时器resolve/reject的then回调,当then回调检测到状态没有发生改变则将回调函数push到各种的队列中,状态发生改变后在从队列中取出回调函数执行。

一、定义回调队列

image.png

二、then回调,若为初始状态则push到回调队列

image.png

三、定时器结束后执行函数时从队列中取出回调函数执行

image.png

链式调用

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到队列中,仔细想想是不是这样呢!

image.png

解决方案:使用 bind 函数传入上下文和对应参数,结果是将bind返回的这个新函数push到队列中,这个新函数其实还是执行原来的 handleThenCb 并且this指向也正确从而完美解决。

image.png

且慢,你是不是忘了then回调是一个微任务,使用了回调队列解决定时器,这里在给then回调封装一层定时器,达到微任务的效果。

image.png

合并效果

 

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)) } }) } }

最后将上述代码思路进行合并看看效果。

image.png

彩蛋:当setTimeout 的延迟时间为0时,then微任务还不够完美...


原文链接:https://juejin.cn/post/7375072812847005706

 

;