从零手撕Promise,掌握Promise的实现原理(13)之手撕Promise的finally方法

643 阅读6分钟

「这是我参与11月更文挑战的第16天,活动详情查看:2021最后一次更文挑战」。

传送门

从零手撕Promise,掌握Promise的实现原理(1)之promise基本结构的实现

从零手撕Promise,掌握Promise的实现原理(2)之基础版本的promise实现

从零手撕Promise,掌握Promise的实现原理(3)之回调地狱是什么

从零手撕Promise,掌握Promise的实现原理(4)之then方法链式调用的初步实现

从零手撕Promise,掌握Promise的实现原理(5)之then方法链式调用的进阶实现 

从零手撕Promise,掌握Promise的实现原理(6)之then方法的回调为什么是异步微任务

从零手撕Promise,掌握Promise的实现原理(7)then方法链式调用之核心方法resolvePromise

从零手撕Promise,掌握Promise的实现原理(8)then方法链式调用之核心方法resolvePromise再探究

从零手撕Promise,掌握Promise的实现原理(9)then方法链式调用之核心方法resolvePromise完全体

从零手撕Promise,掌握Promise的实现原理(10)then方法完全体

从零手撕Promise,掌握Promise的实现原理(11)之测试完全体Promise是否符合PromiseA+

什么?Promise.resolve()还可以返回失败的Promise(12)

回顾

经过上次的文章介绍,我们的Promise已经完成了,catch、reject、resolve方法,现在的Promsie已经长这样了。

const PENDING = 'pending'
const FULFILLED = 'fulfilled'
const REJECTED = 'rejected'
const resolvePromise = (promise, x, resolve, reject) => {
  //*******************************************************判断x与promsie是否是同一个promise,防止进入死循环*********************//////*********
  if(x === promise) throw new TypeError('Chaining cycle detected for promise #<Promise>')
  // 1.首先判断`x`是基础类型数据,还是引用类型,基础类型的数据直接`resolve`,即可。  
  if(x !== null && /^(object|function)$/.test(typeof x)){
    let then 
    // 2. 如果是引用类型的数据,尝试获取`x`上的`then`属性(`x.then`),如果在获取属性的时候报异常则`reject`
    try{
      then = x.then
    }catch(e){
      reject(e)
    }
    //3. 判断`then`是否是函数,如果是一个函数则我们认定它为`Promise`,如果不是则`resolve`
    if(typeof then === 'function'){
      let called = false //**************************************这里加了变量*************************//////
      try{
        then.call(x, (y) => {
          if(called) return //**************************************这里加了变量*************************//////
          called = true
          resolvePromise(promise, y, resolve, reject)
        },(r) => {
          if(called) return//**************************************这里加了变量*************************//////
          reject(r)
        })
      }catch(e){
        if(called) return//**************************************这里加了变量*************************//////
        reject(e)
      }
    }else{
      resolve(x)
    }
  }else{
    //基础类型数据直接resolve
    resolve(x)
  }
}
class Promise{
  constructor(executor){

    this.state = PENDING
    this.value = undefined
    this.reason = undefined
    //存放onFulfilled
    this.onResolvedCallbacks = []
    //存放onRejected
    this.onRejectedCallbacks = []
    const resolve = (value) => {
       //如果发现value是一个Promise,我们需要调用value的then方法去递归解析
      if(value instanceof Promise){
          return value.then(resolve,reject); // 递归解析
      }
      if (this.state === PENDING) {
        this.value = value
        this.state = FULFILLED
        //promise实例状态改变后调用暂存的onFulfilled
        this.onResolvedCallbacks.forEach(fn => fn())
      }
    }

    const reject = (reason) => {
      if (this.state === PENDING) {
        this.reason = reason
        this.state = REJECTED
        //promise实例状态改变后调用的onRejected
        this.onRejectedCallbacks.forEach(fn => fn())
      }
    }
    try {
      //executor函数执行过程中出错,将会导致Promise失败
      executor(resolve,reject)
    } catch (error) {
      reject(error)
    }
  }
  then(onFulfilled, onRejected){
    onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v
    onRejected = typeof onRejected === 'function' ? onRejected : err => { throw err }
    let promise = new Promise((resolve, reject) => {
        
        switch(this.state){
          case FULFILLED:
              setTimeout(() => {
                try{
                  let x = onFulfilled(this.value)
                  resolvePromise(promise, x, resolve, reject)
                } catch(e){
                  reject(e)
                }
              })   
              break
          case REJECTED:
              setTimeout(() => {
                try{
                  let x = onRejected(this.reason)
                  resolvePromise(promise, x, resolve, reject)
                } catch(e){
                  reject(e)
                }
              })   
              break
          default:
            this.onResolvedCallbacks.push(() => {
              setTimeout(() => {
                try{
                  let x = onFulfilled(this.value)
                  resolvePromise(promise, x, resolve, reject)
                } catch(e){
                  reject(e)
                }
              })
            })
            this.onRejectedCallbacks.push(() => {
              setTimeout(() => {
                try{
                  let x = onRejected(this.reason)
                  resolvePromise(promise, x, resolve, reject)
                } catch(e){
                  reject(e)
                }
              })
            })
        }
    })
    return promise
  }
  catch(onRejected){
    return this.then(null, onRejected)
  }
  static rsolve(value){
    return new Promise((resolve,reject)=>{
              resolve(value);
    })
  }
  static reject(reason){
      return new Promise((resolve,reject)=>{
          reject(reason);
      })
  }
}

本篇文章介绍

  • 本篇文章主要完成的是,finally方法,这个方法在面试中也是经常会问到,是怎么实现的。 finally方法是原型上的方法。

手撕finally方法

  • finally方法会接受一个回调函数,不论这个Promise是成功还是失败都会执行这个回调函数。

  • 实现原理,其实finally方法的内部是通过then方法与Promise.resolve来实现的。

  • 原理如下

finally(cb){
  return this.then(
    (y) => {
      return Promise.resolve(cb()).then(() => y)
    },
    (r) => {
      return Promise.resolve(cb()).then(() => {throw r})
    }
  )
}

解析finally实现原理

  • 相信大家和我一样看了上边的实现,觉得脑瓜子嗡嗡的,心里一万个草泥马奔腾,写的特么什么玩意。
  • 下面我将通过四个小事例来解答大家心中的疑惑,因为finally的实现还用到了resolve,为了方便我的表达,我在下面两张图片中标了一些序号方便大家根据我的思路来进行对照。

finally实现的关键代码

11-17-01.png

resolve实现的关键代码

11-17-02.png

11-17-04.png

  • 下面在解释事例的执行逻辑时,用到的序号都是上面两张图片中所标识的,如果不了解resolve的实现过程的,可以通过传送门去看一下以前的文章,专门的介绍了resolve的实现。

  • 事例1

    Promise.resolve('ok').finally(() => { 
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                resolve('inner ok')
            }, 1000);
        })
    }).then((data) => {
        console.log('成功', data)
    }, (err) => {
        console.log('失败', err)
    })
    
    • 原生Promise执行结果(浏览器控制台打印)

      11-17-05.png

    • 根据我们实现的代码来运行这段事例,顺序如下。

      Promise.resolve('ok') ---> 调用finally 执行F1 此时yok---> 执行R1 --->F3最终返回成功的Promsie值是inner ok ---> 执行R2 F1 中的 Promise.resolve 返回了成功的Promise --->执行F5

      此时F5返回的是y(ok),也就是说整个finally到此执行结束返回值是一个成功的Promise 值是ok ---> 调用then执行成功的回调,最后打印 ‘成功 ok

  • 事例2

    Promise.resolve('ok').finally(() => {
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                reject('inner ok')
            }, 1000);
        })
    }).then((data) => {
        console.log('成功', data)
    }, (err) => {
        console.log('失败', err)
    })
    
    • 原生Promise执行结果(浏览器控制台打印)

      11-17-06.png

    • 根据我们实现的代码来运行这段事例,顺序如下。

      Promise.resolve('ok') ---> 调用finally 执行F1 此时yok---> 执行R1 --->F3最终返回失败的Promsie原因是inner ok ---> 执行R3 F1 中的 Promise.resolve 返回了失败的Promise原因是inner ok ---> 此时then并不会执行F5then函数会默认补充第二个失败的回调函数,所以F1会返回失败的Promsie原因是inner ok,也就是说整个finally到此执行结束返回值是一个失败的Promsie原因是inner ok ---> 调用then执行失败的回调,最后打印 ‘失败 inner ok

  • 事例3与事例4 我就不带着大家分析了大家可以自己按照我的方式分析一下,前提是大家自己手写过Promise,不然的话分析起来会很吃力,如果不是很了解Promise,大家可以看我的系列文章,从零开始实现一个Promise

    11-17-07.png

    11-17-08.png

传送门

从零手撕Promise,掌握Promise的实现原理(1)之promise基本结构的实现

从零手撕Promise,掌握Promise的实现原理(2)之基础版本的promise实现

从零手撕Promise,掌握Promise的实现原理(3)之回调地狱是什么

从零手撕Promise,掌握Promise的实现原理(4)之then方法链式调用的初步实现

从零手撕Promise,掌握Promise的实现原理(5)之then方法链式调用的进阶实现 

从零手撕Promise,掌握Promise的实现原理(6)之then方法的回调为什么是异步微任务

从零手撕Promise,掌握Promise的实现原理(7)then方法链式调用之核心方法resolvePromise

从零手撕Promise,掌握Promise的实现原理(8)then方法链式调用之核心方法resolvePromise再探究

从零手撕Promise,掌握Promise的实现原理(9)then方法链式调用之核心方法resolvePromise完全体

从零手撕Promise,掌握Promise的实现原理(10)then方法完全体

从零手撕Promise,掌握Promise的实现原理(11)之测试完全体Promise是否符合PromiseA+

什么?Promise.resolve()还可以返回失败的Promise(12)

后续还会介绍一些其他的Promise方法,希望给个赞。