手写promise-面试版

面试官:"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)
})

因为 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微任务还不够完美...

相关推荐
Rhys..39 分钟前
ES6是什么
前端·javascript·es6
Jammingpro2 小时前
【Vue专题】前端JS基础Part1(含模版字符串、解构赋值、变量常量与对象)
前端·javascript·vue.js
jiangzhihao05156 小时前
前端自动翻译插件webpack-auto-i18n-plugin的使用
前端·webpack·node.js
软件技术NINI8 小时前
html css网页制作成品——HTML+CSS盐津铺子网页设计(5页)附源码
前端·css·html
mapbar_front9 小时前
面试问题—我的问题问完了,你还有什么想问我的吗?
前端·面试
倔强青铜三9 小时前
苦练Python第67天:光速读取任意行,linecache模块解锁文件处理新姿势
人工智能·python·面试
quweiie9 小时前
thinkphp8+layui多图上传,带删除\排序功能
前端·javascript·layui
李鸿耀9 小时前
React 项目 SVG 图标太难管?用这套自动化方案一键搞定!
前端
闲蛋小超人笑嘻嘻9 小时前
树形结构渲染 + 选择(Vue3 + ElementPlus)
前端·javascript·vue.js
我是华为OD~HR~栗栗呀10 小时前
华为od-21届考研-C++面经
java·c语言·c++·python·华为od·华为·面试