手撕(‘chao‘)源码 --- 手写promise

本文详细介绍了如何手动实现Promise,遵循Promise/A+规范,从定义状态、构造Promise实例到实现resolve和reject方法,以及处理then的链式调用。在过程中解决了回调执行时机、状态控制和返回值处理等问题,最后还考虑了避免循环引用的场景。代码实现逐步深入,适合理解Promise的工作原理。
摘要由CSDN通过智能技术生成

手撕(‘chao’)源码 — 手写promise

关于promise的规范参考promise/A+,写的时候没有挨个完全参考,不过会去跑一下promises-tests.那么直接开撕’chao’

首先定义一下的promise的三个状态。

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'

方便实例的操作采取class类定义promise类,并且在构造函数当中初始化状态以及相关值。

class myPromise {
    // promise 接受一个函数参数
    constructor(handle) {
      this.initValue()
      try {
        // try 处理了当handle不为函数的情况,
        // 同时bind绑定到实例上,防止在调用的时候随着函数执行环境this丢失
        handle(this._resolve.bind(this), this._reject(this))
      } catch (err) {
        this._reject(err)
      }
    }
    _isFunction(fn) {
      return typeof fn === 'function'
    }
    // 初始化相关
    initValue() {
      // 初始化状态
      this.state = PENDING
      // 初始化值 .resolve .reject时的值
      this.value = undefined
      // 存放.then .catch时候的函数,
      this.callback = undefined
    }
  }

那么接下来去简单实现一下_resolve和.then方法.

// resolve 接受一个值 在then中可以拿到,而这个值我们就可以保存在this.value中
_resolve(data) {
  this.value = data
  this.state = FULFILLED
  this.callback(this.value)
}

// .then当中接受两个回调,我们就暂时把resolve存放在this.callback上
then(onFulfilled, onRejected) {
  this.callback = onFulfilled
}

这样写后会存在一个问题就是在resolve的时候,onFulfilled还没有到callback上,所以这里可以把callback的执行放在微任务队列里面,同时根据平时promise的写法,这样无法控制resolve后,后面的resolve和reject不执行,所以在这里还需要判断state状态。

_resolve(data) {
  // 这里直接if也行---
  this.state === PENDING &&
    (() => {
      this.value = data
      this.state = FULFILLED
      queueMicrotask(() => {
        this.callback(this.value)
      })
    })()
}
// reject 与resolve同理了
_reject() {
      this.state === PENDING &&
        (() => {
          this.value = data
          this.state = REJECTED
          queueMicrotask(() => {
            this.callback(this.value)
          })
          //this.callback.forEach(fn => fn())
        })()
    }

ok,这样看着好像还可以,resolve基本的逻辑已经完成了,实操一把。(这里说明一下我把myPromise直接挂载到了window的Promise上了

在这里插入图片描述

这样看着好像OK的,那我们考虑下一个功能点,then函数可以返回一个promise对象,而且可以有多个.then。同时在resolve的时候需要把所有的callback回调执行了,那么我们之前的callback就可以转换成一个数组。.then的时候直接追加进callback就可以了。

// resolve 接受一个值 在then中可以拿到,而这个值我们就可以保存在this.value中
_resolve(data) {
  this.state === PENDING &&
    (() => {
      this.value = data
      this.state = FULFILLED
      // queueMicrotask(() => {
      //   this.callback(this.value)
      // })
      this.callback.forEach(fn => fn())
    })()
}
then(onFulfilled, onRejected) {
  let resolve, reject
  const newPromise = new myPromise((nextResolve, nextReject) => {
    resolve = nextResolve
    reject = nextReject
  })
  const callback = () => {
    queueMicrotask(() => {
      // 这里拿到的值可能是上一个,如下图的b = a.then((res) => res+1)
      //那么此时的res应当是上一个resolve的结果
      let newResult = onFulfilled(this.value)
      // 当执行then到这里时,说明已经resolve(onFulfilled)了,那么下一个promise也应当resolve掉
      // 即resolve时,返回的promise对象应当全部resolve掉
      resolve(newResult)
    })
  }
  if (this.state === PENDING) {
    // 说明还没到resolve或者reject 那么直接push到callback当中
    this.callback.push(callback)
  } else {
    // 执行当前then的callback
    callback()
  }

  return newPromise
}

在这里插入图片描述

到这里的话基本就ok了,考虑些特殊的情况,当b = a.then(),此时我没有传进resolve时,即上面代码的onFulfilled为undefined,那么此时promise返回的值就为此时实例中的value,所以我们再加一层判断;同时还有一个点便是,此时我们并没有去判断状态,有可能此时是reject出来的。所以稍加修改之:

// .then当中接受两个回调,我们就暂时把resolve存放在this.callback上
then(onFulfilled, onRejected) {
  let resolve, reject
  const newPromise = new myPromise((nextResolve, nextReject) => {
    resolve = nextResolve
    reject = nextReject
  })
  const callback = () => {
    queueMicrotask(() => {
      let newResult
      if (this.state === FULFILLED) {
        // 此时走的是_resolve
        newResult = this._isFunction(onFulfilled) ? onFulfilled(this.value) : this.value
        resolve(newResult)
      } else if (this._isFunction(onRejected)) {
        // 在这之后走的都是_reject
        newResult = onRejected(this.value)
      } else {
        // 当reject出来之后没有参数接受时
        reject(this.value)
        return
      }
    })
  }
  if (this.state === PENDING) {
    // 说明还没到resolve或者reject 那么直接push到callback当中
    this.callback.push(callback)
  } else {
    // 执行当前then的callback
    callback()
  }

  return newPromise
}

那么接下来就考虑.then回调函数当中返回一个promise对象的情况,这个比较好处理因为我们所有情况都保存到newResult里了所以当是一个promise时,直接then下去就行了。

// .then当中接受两个回调,我们就暂时把resolve存放在this.callback上
then(onFulfilled, onRejected) {
  let resolve, reject
  const newPromise = new myPromise((nextResolve, nextReject) => {
    resolve = nextResolve
    reject = nextReject
  })
  const callback = () => {
    queueMicrotask(() => {
      let newResult
      if (this.state === FULFILLED) {
        // 此时走的是_resolve
        newResult = this._isFunction(onFulfilled) ? onFulfilled(this.value) : this.value
      } else if (this._isFunction(onRejected)) {
        // 在这之后走的都是_reject 对应于 .catch(err => xxx)
        newResult = onRejected(this.value)
      } else {
        // 当reject出来之后没有参数接受时 对应于 .catch() 或者直接没有 .catch 时
        reject(this.value)
        return
      }
      if (newResult instanceof myPromise) {
        newResult.then(resolve, reject)
      } else {  
        resolve(newResult)
      }
    })
  }
  if (this.state === PENDING) {
    // 说明还没到resolve或者reject 那么直接push到callback当中
    this.callback.push(callback)
  } else {
    // 执行当前then的callback
    callback()
  }

  return newPromise
}

测试一下:

看着好像应该还不错,接下来去完善一下测试所需要的方法。不过跑的时候忽略了规范的2.3.3

const myPromise = require('./promise')

const resolved = value => {
  return new myPromise(resolve => {
    resolve(value)
  })
}

const rejected = value => {
  return new myPromise((_, reject) => {
    reject(value)
  })
}

const deferred = () => {
  let resolve, reject
  const promise = new myPromise((_resolve, _reject) => {
    resolve = _resolve
    reject = _reject
  })
  return {
    promise,
    resolve,
    reject
  }
}

module.exports = {
  resolved,
  rejected,
  deferred
}

然后发现

意思返回then的结果不能和原来的相同,可能存在 a = Promise.resolve().then(() => a)
所以在判断then回调是否时promise前添加一条判断:

if (newPromise === newResult) {
  throw TypeError('Chaining cycle detected for promise #<Promise>')
}

在这里插入图片描述

ok,这样的话promise好像差不多’抄完了’~~
上述源码:github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值