面试官:手搓promise

当面试官说出这句话的时候,有些同学感觉可能天都塌了。其实手搓promise并不难,我们得关注promise身上的几个特点

  1. promise中的构造函数要传入一个回调函数,这个回调函数有两个参数resolve,reject分别代表成功和失败状态
  2. promise的then()方法,这个方法只有当前面的promise状态从pending变更为resolve才会依次执行,否则会一直等待。

接下来我们用js中的class来实现一下promise

1. promise

首先先创建一个我们的promise类,并且其中有一个构造函数,在这个构造函数中我们需要实现以下几个功能:

  1. 可以存放promise状态
  2. 存放成功的结果和失败的原因
  3. resolve和reject函数
  4. 如果是pending状态则要将后面的.then()全部储存起来直到状态变更为resolve或者reject后全部执行。

下面我们来实现一下promise的构造函数constructor部分:

js 复制代码
 constructor(executor) {
    this.status = 'pending' // 存放状态
    this.value = null // 存放成功的结果
    this.reason = null // 存放失败的原因
    this.onFullfilledCallbacks = [] // 存放成功的回调
    this.onRejectedCallbacks = [] // 存放失败的回调
    const resolve = (value) => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
        this.onFullfilledCallbacks.forEach(fn => fn(value)) // 执行成功回调
      }
    }
    const reject = (reason) => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'
        this.onRejectedCallbacks.forEach(fn => fn(reason)) // 执行失败回调
      }
    }
    executor(resolve, reject) // 调用传入的箭头函数
}

2. then

在写 promise 的 then 方法的时候我们得注意一点 then 方法的返回值是一个新的 promise,状态为 pending, then的pending状态会根据上一个promise的状态修改而修改。除此之外我们还得注意以下几点

then 的原理:

  1. 接受两个参数,第一个是成功的回调,第二个是失败的回调
  2. 返回一个新的promise
  3. 当 then 执行到的时刻,then前面的promise状态已经变更为fulfilled或rejected,then中的回调函数由then自己执行
  4. 当 then 执行到的时刻,then前面的promise状态是pending,then中的回调函数会先被存放,等待promise状态变更为fulfilled或rejected,再由resolve或reject函数执行

首先我们需要把then挂在promise的原型上面,并且这个方法有两个参数,一个是成功的回调一个是失败的回调。注意,这时候我们在进行后面的逻辑时,需要先判断传入then中的参数是不是方法,如果不是某个方法的话它就在then中啥都不会干,下面给大家展示一下:

js 复制代码
function A() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      console.log('a');
      resolve('a')
    }, 1000);
  })
}
function B() {
  console.log('b');
}
A().then(
  [],
  []
)

// 输出:
// a

在了解完了这一点之后,我们先来完成then函数前面一部分:

js 复制代码
then(onFulfilled, onRejected) {
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
  onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }
  
  let newPromise = new MyPromise((resolve, reject) => {
  
  })
  return newPromise
}

tips: new MyPromise()中一定要传入箭头函数,因为箭头函数中没有this,这样的话才能通过this获取到上一个promise的状态

resolve和reject状态

接下来我们来完成这个传入箭头函数中的逻辑,在这个函数中,我们需要判断上一个promise对象的状态从而执行对应的操作,如果状态是fulfilled或者rejected那么它执行的操作都是一样的,下面我们就来以状态为fulfilled为例子。

首先我们得注意一点,promise.then()这个方法在js中是微任务属于异步代码,但是由于js中的微任务打造太难,所以我们先将其打造成宏任务代码,在这里我们使用setTimeout来包裹里面的逻辑。除此之外,在then()参数中的回调函数中我们还可以获取上一个promise通过resolve出来的结果,而这个结果我们存放在了value当中,所以我们只需要将其传入这个回调函数中并执行这个结果即可。

在我们获取到了then()中回调函数执行的结果之后,我们需要对其进行判断,如果返回出来的结果还是我们所打造的那个Promise,那么我们让其覆盖newPromise并且将这个结果传入构造函数中的resolve从而将其传递给下一个then(),接下来我们来看fulfilled和rejected的代码:

js 复制代码
    if (this.status === 'fulfilled') {
        // 作为异步任务,微任务太难打造所以变成宏任务
        setTimeout(() => {
          try {  
            // 将前面resolve的值传入回调
            const result = onFulfilled(this.value)
            // 判断回调返回的结果是不是Promise
            if (result instanceof MyPromise) {
              newPromise = result
            }
            // 将结果传给下一个then()
            resolve(result)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason) // 作为异步任务,微任务太难打造所以变成宏任务
            if (result instanceof MyPromise) {
              newPromise = result
            }
            resolve(result)
          } catch (error) {
            reject(error)
          }
        })
      }

pending状态

在前文中我们说到,如果promise的状态为pending,就要把后面then中的逻辑全部都放入对应的数组中,当状态发生变更时就清空对应状态的数组,并且其中的代码都是异步的,这时候我们同上需要用setTimeout进行包裹,而这两个数组中的每个函数所执行的逻辑与上文中fulfilled和rejected状态时所执行的逻辑一样。下面我们来看状态为pending状态的时候该执行的逻辑:

js 复制代码
if (this.status === 'pending') {
    this.onFullfilledCallbacks.push((value) => {
      setTimeout(() => {
        try {
          const result = onFulfilled(value)
          if (result instanceof MyPromise) {
            newPromise = result
          }
          resolve(result)
        } catch (error) {
          reject(error)
        }
      });
    })
    this.onRejectedCallbacks.push((reason) => {
      setTimeout(() => {
        try {
          const result = onRejected(value)
          if (result instanceof MyPromise) {
            newPromise = result
          }
          resolve(result)
        } catch (error) {
          reject(error)
        }
      });
    })
}

完整代码

js 复制代码
class MyPromise {
  constructor(executor) {
    this.status = 'pending' // 存放状态
    this.value = null // 存放成功的结果
    this.reason = null // 存放失败的原因
    this.onFullfilledCallbacks = [] // 存放成功的回调
    this.onRejectedCallbacks = [] // 存放失败的回调
    const resolve = (value) => {
      if (this.status === 'pending') {
        this.value = value
        this.status = 'fulfilled'
        this.onFullfilledCallbacks.forEach(fn => fn(value)) // 执行回调
      }
    }
    const reject = (reason) => {
      if (this.status === 'pending') {
        this.reason = reason
        this.status = 'rejected'
        this.onRejectedCallbacks.forEach(fn => fn(reason)) // 执行回调
      }
    }
    executor(resolve, reject)
  }
  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value
    onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason }

    // 返回一个新的promise,这里一定要箭头函数,不然就是这个函数体的不是then的this了
    let newPromise = new MyPromise((resolve, reject) => {
      if (this.status === 'fulfilled') {
        setTimeout(() => {
          try {
            const result = onFulfilled(this.value) // 作为异步任务,微任务太难打造所以变成宏任务
            if (result instanceof MyPromise) {
              newPromise = result
            }
            resolve(result)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === 'rejected') {
        setTimeout(() => {
          try {
            const result = onRejected(this.reason) // 作为异步任务,微任务太难打造所以变成宏任务
            if (result instanceof MyPromise) {
              newPromise = result
            }
            resolve(result)
          } catch (error) {
            reject(error)
          }
        })
      }
      if (this.status === 'pending') {
        this.onFullfilledCallbacks.push((value) => {
          setTimeout(() => {
            try {
              const result = onFulfilled(value)
              if (result instanceof MyPromise) {
                newPromise = result
              }
              resolve(result)
            } catch (error) {
              reject(error)
            }
          });
        })
        this.onRejectedCallbacks.push((reason) => {
          setTimeout(() => {
            try {
              const result = onRejected(value)
              if (result instanceof MyPromise) {
                newPromise = result
              }
              resolve(result)
            } catch (error) {
              reject(error)
            }
          });
        })
      }
    })
    return newPromise
  }
}

let p = new MyPromise((resolve, reject) => {
  setTimeout(() => {
    console.log(22);
  }, 1000);
  resolve('success')
})
p.then(res => {
  console.log(res);
})

总结

  • promise中的构造函数要传入一个回调函数,这个回调函数有两个参数resolve,reject分别代表成功和失败状态
  • promise的then()方法,这个方法只有当前面的promise状态从pending变更为resolve才会依次执行,否则会一直等待。
  • then 方法的返回值是一个新的 promise,状态为 pending, then的pending状态会根据上一个promise的状态修改而修改

then 的原理:

  1. 接受两个参数,第一个是成功的回调,第二个是失败的回调
  2. 返回一个新的promise
  3. 当 then 执行到的时刻,then前面的promise状态已经变更为fulfilled或rejected,then中的回调函数由then自己执行
  4. 当 then 执行到的时刻,then前面的promise状态是pending,then中的回调函数会先被存放,等待promise状态变更为fulfilled或rejected,再由resolve或reject函数执行
相关推荐
xyd陈宇阳几秒前
嵌入式开发高频面试题全解析:从基础编程到内存操作核心知识点实战
c语言·数据结构·stm32·算法·面试
2501_915373883 分钟前
electron+vite+vue3 快速入门教程
前端·javascript·electron
zhl99999999913 分钟前
xe-upload上传文件插件
前端·上传文件·xe-upload·兼容app上传文件
宁酱醇2 小时前
CSS基础_@拉钩教育【笔记】
前端·css
建群新人小猿2 小时前
CRMEB-PRO系统定时任务扩展开发指南
android·java·开发语言·前端
牧天白衣.2 小时前
vue 和 html 的区别
前端
知识分享小能手2 小时前
JavaScript学习教程,从入门到精通,Ajax数据交换格式与跨域处理(26)
xml·开发语言·前端·javascript·学习·ajax·css3
好名字08212 小时前
el-tabs与table样式冲突导致高度失效问题解决(vue2+elementui)
前端·vue.js·elementui
qq_278063712 小时前
vue elementui 去掉默认填充 密码input导致的默认填充
前端·vue.js·elementui
黄同学real3 小时前
HTML5 新增的主要标签整理
前端·html·html5