剖析Promise,你值得了解 !

92 阅读10分钟

Promise

u=3684334832,529868676&fm=253&fmt=auto&app=138&f=PNG-1.webp Promise是ES6新增的一个对象,代表了即将要发生的事情,是在处理异步过程中传达信息的一种方式,

它有哪些特性呢?可以去看看我以前写过的同步与异步 Promise这篇文章。但是 首先我们需要知道,它的状态不受外界的影响,而Promise里有三个状态

  • Pending: 等待状态,可以转换成resolve成功态,和reject拒绝态
  • Resolve: 成功态,表示操作成功,可以抛出相应的内容
  • Reject: 拒绝态,表示操作失败,也可以抛出相应的报错信息

那么我们就根据Promise的功能,自己写一个Promsie对象

我们先定义Promise对象的三个状态,这叫做常量管理,使用大写字母替代

const PENDING = 'pending'

const RESOLVED = 'resolved'

const REJECTED = 'rejected'

我们知道Promise是一个对象,且可以被关键字new使用,那么Promise就作为一个构造函数使用,其中具有resolve回调方法,reject回调方法,并且转换为成功态或者拒绝态后会抛出相应的回调,给与.then方法使用,而回调可能不止一个,所以我们采用两个数组来装载相应的回调,所以不难写出以下代码框架。

function MyPromise(fn) {
    
    const that = this   //绑定this为调用该Promise的对象的作用域,获取正确的this对象
    
    this.value = null  //resolve或者reject的参数
    
    that.resolvedCallbacks = [] // 存放then里面的回调函数1  

    that.rejectedCallbacks = [] // catch里面的回调函数2
   
    this.state = PENDING

   function resolve(value) {
   
   }

   function reject(value) {
   
   }
   
   fn(resolve, reject)
}

往MyPromise的原型上挂上.then方法,接收回调,成功态触发onFullfilled回调,拒绝态触发onRejected回调,并执行

MyPromise.prototype.then = function(onFulfilled, onRejected) {
}

我们在搭好需要的框架后,慢慢地编写细节。首先,resolve,reject的状态是不允许逆向修改的,只能由pengding态转变为resolve或reject态,所以在此之前需要判定当前状态是否为Pending状态,若为其他的状态,则不能转换执行相应的回调。否则就该执行相应的回调,同时考虑到可能会有多个回调,所以我们需要遍历相应的回调数组,并执行,

同时fn的执行可能成功,可能失败,严谨性考虑,采用try catch 语句

    function resolve(value) {

       if (that.state === PENDING) {

         that.state = RESOLVED

         that.value = value  //获取到该参数   

         that.resolvedCallbacks.map(cb => cb(that.value))//可能接收多个回调

      }
   }

   function reject(value) {
   
      if (that.state === PENDING) {

        that.state = REJECTED

        that.value = value

        that.rejectedCallbacks.map(cb => cb(that.value))

      }
      
   }
   
   try { //调用成功

    fn(resolve, reject)

  } catch (error) {

    reject(error)

  }

之后我们编写.then方法,首先.then方法当中的参数是否是个函数,需要进行判定,防止后续方法出现问题。状态变更的时候,相应的状态发生改变,.then函数需要马上执行,如果还是pending状态,则需要将回调压进相应状态的数组,如果状态已经发生变更,则执行相应状态下的方法

MyPromise.prototype.then = function(onFulfilled, onRejected) {

   const that = this

   const promise2 = null

  //传来的参数必须是个函数

   onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v

   onRejected = typeof onRejected === 'function' ? onRejected : r => {

    throw r

  }
  
  if (that.state === PENDING) {
  
     that.resolvedCallbacks.push(onFulfilled)

     that.rejectedCallbacks.push(onRejected)
  }
  
  if (that.state === RESOLVED) {
  
     onFulfilled(that.value)
  
  }
  
  if (that.state === REJECTED) {

     onRejected(that.value)

  }
  
}

但是如果遇到下列代码

new Promise((resolve, reject) => {

    setTimeout(() => {

        resolve(1)

    },1000)

}).then(value => {

    console.log(value);

})

定时器当中调用resolve回调,而由于setTimeout异步在event-loop执行顺序当中是在异步任务当中的宏任务,.then函数是微任务,所以先执行,而在此时状态迟迟没有变更,那么就不能够调用相应的回调并且触发.then里的回调,所以需要先判断pengding状态,将相应的回调装进回调数组起来,等到状态变更之后再次触发。

调用

那么写到这里,已经是小小的一步成功,我们采用以下方法来调用自己写的MyPromise方法,先从pengding态转换为拒绝态,之后再利用定时器,看看是否能再次转换为成功态,来表明我们自己写的MyPromise是否成功

const p = new MyPromise((resolve, reject) => {

    reject('no')

    setTimeout(() => {

        resolve('yes')

    }, 1000)

})

p.then(res => {

    console.log(res);

},

    err => {

        console.log(err); //no

    })

**结果证明,我们写的代码是正确的,但是这只是精简版本的,所有代码如下:但是对于想要入大厂的你们来说,远远不够。

const PENDING = 'pending'
const RESOLVED = 'resolved'
const REJECTED = 'rejected'

function MyPromise(fn) {
  const that = this
  that.state = PENDING
  that.value = null // 存放resolve()内部的参数
  that.resolvedCallbacks = [] // then里面的回调函数1
  that.rejectedCallbacks = [] // then里面的回调函数1

  function resolve(value) {
    if (that.state === PENDING) {
      that.state = RESOLVED
      that.value = value
      that.resolvedCallbacks.map(cb => cb(that.value))
    }
  }

  function reject(value) {
    if (that.state === PENDING) {
      that.state = REJECTED
      that.value = value
      that.rejectedCallbacks.map(cb => cb(that.value))
    }
  }

  try {
    fn(resolve, reject)
  } catch (error) {
    reject(error)
  }
}

MyPromise.prototype.then = function(onFulfilled, onRejected) {
  const that = this
  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
  onRejected = typeof onRejected === 'function' ? onRejected : r => {
    throw r
  }

  if (that.state === PENDING) {
    that.resolvedCallbacks.push(onFulfilled)
    that.rejectedCallbacks.push(onRejected)
  }

  if (that.state === RESOLVED) {
    onFulfilled(that.value)
  }
  if (that.state === REJECTED) {
    onRejected(that.value)
  }
}

进阶版

我们都知道Promise同样可以返回一个对象,进行链式调用.then方法。所以我们需要加以判定value是否也是一个MyPromise对象,如果是,我们就直接在里面调用.then方法,传进相应的回调函数参数,而为了保证正确的函数执行顺序,所以我们需要用定时器将后续代码包裹起来

function MyPromise(fn) {

  const that = this  //防止作用域被修改,获取正确的this对象

  that.state = PENDING

  that.value = null // 存放resolve()内部的参数  value作为中转站 与then相连

  that.resolvedCallbacks = [] // 存放then里面的回调函数1  

  that.rejectedCallbacks = [] // catch里面的回调函数2
  
  function resolve(value) {

    //value是否为MyPromise这个类型的实例对象   .then后面调用.then

    if (value instanceof MyPromise) {

      return value.then(resolve, reject)

    }

    //去到下次的任务队列  保证then的执行顺序

    setTimeout(() => { //状态发生变更就要回调

      if (that.state === PENDING) {

        that.state = RESOLVED

        that.value = value  //获取到该参数   可能接收多个回调

        that.resolvedCallbacks.map(cb => cb(that.value))

      }

    }, 0)

  }

  function reject(value) {

    setTimeout(() => { 

      if (that.state === PENDING) {

        that.state = REJECTED

        that.value = value

        that.rejectedCallbacks.map(cb => cb(that.value))

      }

    }, 0)

  }

  try { //调用成功

    fn(resolve, reject)

  } catch (error) {

    reject(error)

  }

}

之后我们更改.then函数当中的内容,我们定义一个promise2对象,表示返回的一个新的promsie对象,如果状态还是pengding,需要先保存新的promise对象,并且push相应的回调函数,执行之后返回一个x,为了不同的promise兼容使用,创建resolutionProcedure新函数,将新的执行结果x和promise2对象传入,抛出给.then函数使用,等待状态变更之后再执行。而为了代码严谨性,也要用try catch包裹。。

//原型挂上then方法      接收回调resolve reject

MyPromise.prototype.then = function(onFulfilled, onRejected) {

  const that = this

  const promise2 = null

  //传来的参数必须是个函数

  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v

  onRejected = typeof onRejected === 'function' ? onRejected : r => {

    throw r

  }

  //判断当前的状态

  if (that.state === PENDING) {

    return (promise2 = new MyPromise((resolve, reject) => {

      that.resolvedCallbacks.push(() => {

        try {

          const x = onFulfilled(that.value)

          resolutionProcedure(promise2, x, resolve, reject)

        } catch (error) {

          reject(error)

        }

      })

      that.rejectedCallbacks.push(() => {

        try {

           //下个promise的回调   x为   第一层.then的执行结果

          const x = onRejected(that.value)

          //放置第二层执行.then

          resolutionProcedure(promise2, x, resolve, reject)

        } catch (error) {

          reject(error)

        }

      })

    }))

    // that.resolvedCallbacks.push(onFulfilled)

    // that.rejectedCallbacks.push(onRejected)

  }

   //状态已经变更  就调用方法

  if (that.state === RESOLVED) {

    //返回一个新的promise对象

    return (promise2 = new MyPromise((resolve, reject) => {

      setTimeout(() => {

        try {

          //下个promise的回调   x为   第一层.then的执行结果

          const x = onFulfilled(that.value)

          //放置第二层执行.then

          resolutionProcedure(promise2, x, resolve, reject)

        } catch (error) {

          reject(error)

        }

      })

    }))

    // onFulfilled(that.value)

  }

  if (that.state === REJECTED) {

    onRejected(that.value)

    // ...

  }

}

我们在resolutionProcedure函数当中,兼容使用多种不同的promise,需要判断当前的promise和上一次.then执行抛出的promise对象是否相同,相同就不予执行,抛出类型错误,避免发生循环引用的问题。就比如下面的代码,p对象的.then函数执行后,抛出了p1对象,也就是x,若是与当前promise相同,那么p1再次调用自身,就会反复执行p1,进入循环,所以需要判定

let p = new Promise((resolve, reject) => {
  resolve(1)
})
let p1 = p.then(value => {
   return p1
}}

之后我们还需要判断x的类型,x是MyPromise对象,才可以继续抛出promise对象,并接收相应的回调,继续执行下一个.then函数,而下一个.then接收的是上一个.then抛出来的值,这是我们需要注意的。

若是遇到这种调用MyPromise的代码,也是被允许的,所以我们需要完善。

.then(value => {

    return new Promise().then(() => {

    })

})

为了应对上述代码的调用,我们有了应对之策,上述代码当中抛出的promise对象就是x。而上一次.then方法的执行结果x不为空,且x为对象或者方法,那么需要获取到x的.then,如果也是一个方法,那么就执行,同时利用called这个标记,判断是否已经调用过,确保回调只会执行一次.此时需要再次判断抛出的是否也为函数,如果是,抛出resolutionProcedure(promise2, x, resolve, reject)继续进行;不是函数,那么直接resolve(x),抛出内容。

function resolutionProcedure(promise2, x, resolve, reject) {

  //前一个.then 和后一个 .then接收的对象不能一样

  if (promise2 === x) { //不能抛出同一个promise对象

    return reject(new TypeError('Error'))

  }

  if (x instanceof MyPromise) { //仍然是一个promise对象,继续抛出该对象,给下一个.then

    x.then(function(value) {

      resolutionProcedure(promise2, value, resolve, reject)

    })

  }

  let called = false  //控制执行回调仅为一次

  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {

    try {

      let then = x.then////考虑可能会return new Promise.then({}),获取x的then方法

      if (typeof then === 'function') {

        then.call(

          x,

          y => { //只执行一次回调

            if (called) return

            called = true

            resolutionProcedure(promise2, y, resolve, reject)

          },

          e => {

            if (called) return

            called = true

            reject(e)

          }

        )

      } else { //x不是函数

        resolve(x)  //说明不是promise对象,直接resolve

      }

    } catch (error) {

      if (called) return

      called = true

      reject(error)

    }

  } else {

    resolve(x)

  }

}

完整规范代码

const PENDING = 'pending'

const RESOLVED = 'resolved'

const REJECTED = 'rejected'


function MyPromise(fn) {

  const that = this  //防止作用域被修改,获取正确的this对象

  that.state = PENDING

  that.value = null // 存放resolve()内部的参数  value作为中转站 与then相连

  that.resolvedCallbacks = [] // 存放then里面的回调函数1  

  that.rejectedCallbacks = [] // catch里面的回调函数2

  function resolve(value) {

    //value是否为MyPromise这个类型的实例对象   .then后面调用.then

    if (value instanceof MyPromise) {

      return value.then(resolve, reject)

    }

    //去到下次的任务队列  保证then的执行顺序

    setTimeout(() => { //状态发生变更就要回调

      if (that.state === PENDING) {

        that.state = RESOLVED

        that.value = value  //获取到该参数   可能接收多个回调

        that.resolvedCallbacks.map(cb => cb(that.value))

      }

    }, 0)

  }


  function reject(value) {

    setTimeout(() => { //

      if (that.state === PENDING) {

        that.state = REJECTED

        that.value = value

        that.rejectedCallbacks.map(cb => cb(that.value))

      }

    }, 0)

  }


  try { //调用成功

    fn(resolve, reject)

  } catch (error) {

    reject(error)

  }

}


//原型挂上then方法      接收回调resolve reject

MyPromise.prototype.then = function(onFulfilled, onRejected) {

  const that = this

  const promise2 = null

  //传来的参数必须是个函数

  onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v

  onRejected = typeof onRejected === 'function' ? onRejected : r => {

    throw r

  }


  //判断当前的状态

  if (that.state === PENDING) {

    return (promise2 = new MyPromise((resolve, reject) => {

      that.resolvedCallbacks.push(() => {

        try {

          const x = onFulfilled(that.value)

          resolutionProcedure(promise2, x, resolve, reject)

        } catch (error) {

          reject(error)

        }

      })


      that.rejectedCallbacks.push(() => {

        try {

           //下个promise的回调   x为   第一层.then的执行结果

          const x = onRejected(that.value)

          //放置第二层执行.then

          resolutionProcedure(promise2, x, resolve, reject)

        } catch (error) {

          reject(error)

        }

      })

    }))

    // that.resolvedCallbacks.push(onFulfilled)

    // that.rejectedCallbacks.push(onRejected)

  }

   //状态已经变更  就调用方法

  if (that.state === RESOLVED) {

    //返回一个新的promise对象

    return (promise2 = new MyPromise((resolve, reject) => {

      setTimeout(() => {

        try {

          //下个promise的回调   x为   第一层.then的执行结果

          const x = onFulfilled(that.value)

          //放置第二层执行.then

          resolutionProcedure(promise2, x, resolve, reject)

        } catch (error) {

          reject(error)

        }

      })

    }))

    // onFulfilled(that.value)

  }

  if (that.state === REJECTED) {

    onRejected(that.value)

    // ...

  }

}


function resolutionProcedure(promise2, x, resolve, reject) {

  //前一个.then 和后一个 .then接收的对象不能一样

  if (promise2 === x) { //不能抛出同一个promise对象

    return reject(new TypeError('Error'))

  }

  if (x instanceof MyPromise) { //仍然是一个promise对象,继续抛出该对象,给下一个.then

    x.then(function(value) {

      resolutionProcedure(promise2, value, resolve, reject)

    })

  }


  let called = false  //控制执行回调仅为一次

  if (x !== null && (typeof x === 'object' || typeof x === 'function')) {

    try {

      let then = x.then////考虑可能会return new Promise.then({}),获取x的then方法

      if (typeof then === 'function') {

        then.call(

          x,

          y => { //只执行一次回调

            if (called) return

            called = true

            resolutionProcedure(promise2, y, resolve, reject)

          },

          e => {

            if (called) return

            called = true

            reject(e)

          }

        )

      } else { //x不是函数

        resolve(x)  //说明不是promise对象,直接resolve

      }

    } catch (error) {

      if (called) return

      called = true

      reject(error)

    }

  } else {

    resolve(x)

  }

}

总结

以上也只是个人理解,如有错误,请大家在评论当中指出,谢谢!我是小白,在js路上不折不扣!