面试官:手搓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函数执行
相关推荐
脑花儿15 小时前
ABAP SMW0下载Excel模板并填充&&剪切板方式粘贴
java·前端·数据库
闭着眼睛学算法16 小时前
【华为OD机考正在更新】2025年双机位A卷真题【完全原创题解 | 详细考点分类 | 不断更新题目 | 六种主流语言Py+Java+Cpp+C+Js+Go】
java·c语言·javascript·c++·python·算法·华为od
烛阴16 小时前
【TS 设计模式完全指南】构建你的专属“通知中心”:深入观察者模式
javascript·设计模式·typescript
ShineSpark16 小时前
C++面试11——指针与引用
c++·面试
lumi.16 小时前
Vue.js 从入门到实践1:环境搭建、数据绑定与条件渲染
前端·javascript·vue.js
二十雨辰16 小时前
vue核心原理实现
前端·javascript·vue.js
影子信息16 小时前
[Vue warn]: Error in mounted hook: “ReferenceError: Jessibuca is not defined“
前端·javascript·vue.js
卷Java17 小时前
CSS模板语法修复总结
java·前端·css·数据库·微信小程序·uni-app·springboot
北城以北888817 小时前
JavaScript--基础ES(一)
开发语言·javascript·intellij-idea
ThreeAu.17 小时前
2025年Web自动化测试与Selenium面试题收集:从基础到进阶的全方位解析
自动化测试·软件测试·selenium·测试工具·面试·web测试·测试开发工程师