手写promise

78 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第9天,点击查看活动详情

前言

大家好,我是小阵 🔥,一路奔波不停的码字业务员
身为一个前端小菜鸟,总是有一个飞高飞远的梦想,因此,每点小成长,我都想要让它变得更有意义,为了自己,也为了更多值得的人
如果喜欢我的文章,可以关注 ➕ 点赞,与我一同成长吧~😋
加我微信:zzz886885,邀你进群,一起学习交流,摸鱼学习两不误🌟

开开心心学技术大法~~

开心

来了来了,他真的来了~

正文

分析promise

  • promise内部有状态promiseStatus
    • fulfiled 成功状态
    • rejected 失败状态
    • pending 等待状态
  • promiseStatus状态只有第一次生效
    • pending变为resolve之后不会再次变为reject或pending
    • pending变为reject之后不会再次变为resolve或pending
  • promise内部有结果promiseResult
    • 初始值是null
    • 成功时是resolve传入的结果
    • 失败时是error或reject传入的结果
  • promise有resolve和reject
    • resolve触发才表示promise成功,并且可以往下走
    • promise内部有错误触发reject,终止执行
  • promise有then
    • then中可传入新的resolve和reject
    • then之后返回一个promise
    • then可以链式调用
    • 当promise中有异步任务时依然可以按照顺序链式执行then
  • promise是微任务

具体实现

从定义属性开始

class myPromise {
  constructor(executor) {
    this.initValue()
    this.initBind()
    executor(this.resolve, this.reject)
  }
  initValue() {
    this.promiseResult = null;
    this.promiseStatus = 'pending';
  }
  initBind() {
    this.resolve = this.resolve.bind(this)
    this.reject = this.reject.bind(this)
    this.then = this.then.bind(this)
  }
  resolve(res) {
    this.promiseResult = res;
    this.promiseStatus = 'fulfilled'
  }
  reject(err) {
    this.promiseResult = err;
    this.promiseStatus = 'rejected'
  }
  then(resolve, reject) {
    const thenPromise = new myPromise((_resolve, _reject) => {
      if (this.promiseStatus === 'fulfilled') {
        resolvePromise(resolve)
      } else if (this.promiseStatus === 'rejected') {
        resolvePromise(reject)
      }
    return thenPromise;
  }
}

上面我们做了哪些事情?

  1. 在new myPromise的时候将callback拿到并且立即实行,并且将myPromise内部的resolve和reject传入到callback中。

  2. 初始化myPromise内部的promiseResult和promiseStatus。

  3. 绑定resolve和reject、then的this,这个很重要,因为会涉及到then的链式调用,所以绑定this至关重要,也可以避免一些this指向的问题

promise状态只能改变一次

因为promise的状态只能被改变一次,因此需要在resolve和reject中添加校验逻辑

if (this.promiseStatus !== 'pending') return;

只有从pending转变成其他状态时才往下执行,否则直接return

promsie内部报错,直接reject

    try {
      executor(this.resolve, this.reject)
      // executor.call(this, this.resolve, this.reject)
    } catch (err) {
      this.reject(err)
    }

promise支持异步调用

promise内部可执行异步代码且then能保持链式执行。思考一下,我们new出来的实例上一旦挂在then函数,那我们是不是就直接可以拿到这个方法,我们将方法存储在myPromise内部,当resolve导致的promiseResult和promsieStatus变化之后再执行之前存储起来的方法不就可以了吗?

另外,因为之后要支持then的链式调用,因此建议通过数组来存储方法

这里需要补充一点,尽管then被存到了数组中,但是数组中只会有一个then方法,那其他链式调用的then被挂到哪里去了呢?思考一下,每一个then都会返回一个新的promise,那当然新promise的then会挂到这个新的promise上啦。所以通过数组或对象来存储都可以,具体看个人

  initValue() {
    this.fulfilledCallbacks = [];
    this.rejectedCallbacks = [];
  }
  
  then(resolve, reject) {
    const thenPromise = new myPromise((_resolve, _reject) => {
      if (this.promiseStatus === 'fulfilled') {
        resolvePromise(resolve)
      } else if (this.promiseStatus === 'rejected') {
        resolvePromise(reject)
      } else {
        this.fulfilledCallbacks.push(resolve)
        this.rejectedCallbacks.push(reject)
      }
    return thenPromise;
  }

promise的then链式调用

链式调用的关键是this。

首先我们应该明白,then方法返回一个promise,返回的promise保存下一个then的callback,然后通过callback继续返回一个新的promise。

其中在每一个then方法中,this都应该指向前面的promise,只有如此,才可以在then方法体中安全的拿到promiseStatus和promiseResult。

同时又因为then方法体返回的promise中保存有下一个then的callback,也就是新的resole和reject,所以可以在由本次then来判定如何在合适的实际调用新的resolve和reject。

从而一层层嵌套下去。

  then(resolve, reject) {
    // 这里的this是外层promise的this,无需多言
    // 这个resolve是本次then的resolve
    const resolveCallback = resolve;
    // 这个promise里的resolve是本次then之后返回的promise里的resolve
    // 也就是是本次then之后then的callback
    const thenPromise = new myPromise((_resolve, _reject) => {
      // 这里的this依然是外层promise的this,因为箭头函数自动绑定this
      // 或者写成 (function(_resolve,_reject){}).bind(this)
      const resolvePromise = (cb) => {
        // 这里的this依然还是是外层promise的this,因为箭头函数自动定义函数时外部的this,针对执行resolvePromise时this变化的情况
        try {
          const t = cb(this.promiseResult)
          if (t instanceof myPromise) {
            // 说明返回的是myPromise
            t.then(_resolve, _reject)
          } else {
            _resolve(t)
          }
        } catch (err) {
          // 如果then中传入的resolve方法体中有报错,则先执行本次then的reject
          reject(err)
          // 然后再触发下一个then的catch
          _reject(err)
          // throw new Error(err)
        }
      }

      if (this.promiseStatus === 'fulfilled') {
        resolvePromise(resolveCallback)
      } else if (this.promiseStatus === 'rejected') {
        resolvePromise(reject)
      } else {
        // 这里的关键不是bind this(当然,前提是前面resolvePromise要是箭头函数,或者已经手动绑定过this了),而是既要将resolveCallback或reject传入resovlePromise,又要不执行
        // 我们尝试了以下三种写法,都是没问题的,我们随便用一种写法
        // 思考以下,为什么不bind this的情况下,this依然可以指到外层promise?
        // 因为resolvePromise(xx)被传入到数组中,真正执行时依然是在外层promise的resolve中,所以这个时候this依然执行外层promise
        // this.fulfilledCallbacks.push(function () { resolvePromise(resolveCallback) })
        // this.fulfilledCallbacks.push(() => resolvePromise(resolveCallback))
        this.fulfilledCallbacks.push(resolvePromise.bind(this, resolveCallback))


        // this.rejectedCallbacks.push(resolvePromise.bind(this, reject))
        // this.rejectedCallbacks.push(pushFn)
        this.rejectedCallbacks.push(function () { resolvePromise(reject) })
      }

    })
    return thenPromise;
  }
}

因为then方法是promise实现的关键,所有我补充了很多注释,不懂的小伙伴可以自己写一遍,才可以更深刻的理解到。

完整代码

享受成果

console.log('11')
const test = new myPromise((resolve, reject) => {
  setTimeout(() => {
    resolve('是是是')
  }, 1000)
})
  .then((res) => {
    console.log('第一个then的打印', res)
    // throw('sdd')
    // return {}
    return 1
  })
  .then(
    function (res) {
      console.log('第二个then的打印', res)
      // return 2
      // throw ('第二个then的throw')
      return new myPromise((resolve, reject) => {
        // reject('错误')
        setTimeout(() => {
          resolve('我是第二个then的promiseResult')
        }, 1000);
      })
    },
    err => console.log('我是第二个then的catch', err)
  )
  .then(
    function (res) {
      console.log('我是第三个then的打印', res)
      reject('错误')
    },
    err => console.log('我是第三个then的catch', err)
  )
  .then(
    res => {
      console.log('我是第四个then的打印', res)
    },
    err => console.log('我是第四个catch', err)
  )
// console.log(test)
console.log('22')

promise动效.gif

结语

往期好文推荐「我不推荐下,大家可能就错过了史上最牛逼vscode插件集合啦!!!(嘎嘎~)😄」