首页 > 其他分享 >手写Promise主要思路详解

手写Promise主要思路详解

时间:2022-09-05 20:56:49浏览次数:62  
标签:resolve 函数 PromiseState 详解 onResolve reject Promise 手写

基本框架

要想手写Promise,我们就要先了解Promise到底实现了什么,先从最基础的地方开始。

Promise最基本的地方就是传入一个函数形式的参数,在then方法传入对于该函数所对应的成功或者失败的回调函数

let p = new Promise((resolve, reject) => {
      resolve('OK')
    })

    p.then(
      value => {
    	console.log(value)
    },reason => {
        console.log(reason)
      })

因此,我们手写需要在自制的Promise函数内部添加原型方法then,在constructor构造器函数内部添加需要传入以函数作为参数的executor变量

class Promise {
  constructor(executor) {
  }

  //添加then方法
  then(onResolve, onReject) {
    
  }
}

同时,then方法也需要接收两个以函数作为参数的变量,我们分别命名为 onResolveonReject

resolve和reject方法构建

Promise中说明了执行器函数即executor在内部是同步调用的,因此我们直接在constructor中对executor进行调用

constructor(executor) {
  executor();
}

在调用的时候,我们发现官方Promise函数的执行器函数会传入两个参数 resolve 和 reject,因此,我们需要在executor中传入这两个参数,但是光传入还不行,我们还需要对这两个参数进行声明,我们通过观察官方的Promise函数内部resolve和reject的调用可以发现,这两个参数明显是两个函数类型的数据,且该函数还接受了一个参数,所以我们需要以函数形式声明resolve和reject:

constructor(executor) {
  const resolve = function(data){};
  const reject = function(data){};
  executor(resolve,reject);
}

resolve和reject代码实现

我们研究resolve函数可以发现它主要做到两个事情:

  1. 在调用时修改Promise实例对象的状态为 fulfill

    fulfill 和 resolved 都表示成功的状态

  2. 在调用时修改Promise实例对象的结果为它本身接收的参数。

说到实例对象的状态和结果,打印过Promise实例对象的人都知道,状态和结果是该实例对象上的两个属性

image

因此,我们在Promise身上也设置两个属性 PromiseStatePrmiseResult ,并且让resolve在调用时修改这两个属性的值。

constructor(executor) {
    this.PromiseState = 'pending'
    this.PrmiseResult = null
    //resolve函数
    const resolve = (data) => {
      //1.修改状态(promiseState)
      this.PromiseState = 'fulfilled'
      //2.设置结果值(promiseResult)
      this.PrmiseResult = data
    }
    //reject函数同理
    ······
    executor(resolve,reject);
  }

注意由于this指向问题,我们将resolve和reject的函数从匿名函数改为箭头函数。

reject的写法相同,只是将状态改为rejected即可。

状态只允许修改一次

官方的Promise在使用中我们还能发现,已经修改过Promise状态的实例对象无法再次修改状态

let p = new Promise((resolve, reject) => {
      resolve('OK');
      reject('BAD')
    })

如上述代码所示,最后实例对象返回状态为 fulfill,返回值为 OK,下面的reject函数不会执行。

因此我们在resolve中插入if判断,从而保证只修改一次状态

const resolve = (data) => {
      //1.修改状态(promiseState)
      if (this.PromiseState != 'pending') return // 保证只修改一次
      this.PromiseState = 'fulfilled'
      //2.设置结果值(promiseResult)
      this.PrmiseResult = data
      }
    }

throw抛出改变状态

官方的Promise除了resolve和reject能改变状态以外,throw的抛出也能改变Promise实例对象的状态,因此我们也需要加上该功能:

constructor(executor) {
    this.PromiseState = 'pending'
    this.PrmiseResult = null
    //resolve实现函数
    ·······
    //reject实现函数
    ·······
    //捕获throw抛出的错误
    try {
      executor(resolve, reject)
    } catch (error) {
      reject(error)//调用reject将状态改为失败
    }
  }

then方法回调使用

官方的Promise.then方法中会传入两个参数,成功调用第一个参数,而失败调用第二个参数,因此我们在Promise中使用if来实现该功能:

  //添加then方法
  then(onResolve, onReject) {
    if (this.PromiseState == 'fulfilled') {
      onResolve(this.PrmiseResult);
    }
    if (this.PromiseState == 'rejected') {
      onReject(this.PrmiseResult);
    }
  }

同样的,我们除了使用 PromiseState 判断以外,还要将结果 PrmiseResult 传入以供函数调用。

异步任务执行

我们使用的Promise很多情况下执行的都是异步任务,比如http请求处理,文件的调用,最简单的也有定时器的执行之类的异步任务,而要求是即使是异步任务,Promise也会等待任务完成之后再返回结果和状态改变。

  //添加then方法
  then(onResolve, onReject) {
    if (this.PromiseState == 'fulfilled') {
      onResolve(this.PrmiseResult);
    }
    if (this.PromiseState == 'rejected') {
      onReject(this.PrmiseResult);
    }
    //判断pending状态,如果此时状态还为pending则交给resolve和reject,在这两个函数调用的时候调用回调
      if (this.PromiseState == 'pending') {
        //·······
      }
  }

而进入pending的if中后,我们究竟应该做些什么呢,我们知道无论是onResolve 还是onReject ,都需要在状态改变后调用,也就是在 resolve 或者reject 函数调用后调用,而这两个函数并不是在同一个作用域内,我们无法直接在resolve中调用 onResolve ,因此,我们在pending的if中该做的就是保存回调函数。

同样,回调函数保存的位置我们保存在Promise 中,这样比较安全。

class Promise {
  constructor(executor) {
    this.PromiseState = 'pending'
    this.PrmiseResult = null
    this.callback = {} //用于存储回调函数
    //resolve函数
    ······
    //reject函数
    ······
    //捕获throw跑出的错误
    ······
  }
  //添加then方法
  then(onResolve, onReject) {
    if (this.PromiseState == 'fulfilled') {
      onResolve(this.PrmiseResult);
    }
    if (this.PromiseState == 'rejected') {
      onReject(this.PrmiseResult);
    }
    //判断pending状态,如果此时状态还为pending则交给resolve和reject,在这两个函数调用的时候调用回调
      if (this.PromiseState == 'pending') {
        //保存回调函数
        this.callback = {
          onResolve,
          onReject //这里使用了es6的键值同名,省略键的操作
        }
      }
  }
}

保存完之后,我们需要在resolve或者reject中进行调用

const resolve = (data) => {
  //1.修改状态(promiseState)
  if (this.PromiseState != 'pending') return // 保证只修改一次
  this.PromiseState = 'fulfilled'
  //2.设置结果值(promiseResult)
  this.PrmiseResult = data
  //执行回调
  if(this.callback.onResolve){
  	this.callback.onResolve(data)
  }
}

reject函数同理,就不多赘述

实现多个回调

我们都知道,当为同一个Promise实例对象指定多个回调的时候,只要状态发生改变,多个回调全都会执行,演示如下:

let p = new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve('OK')
      }, 2000)
    })

    p.then(value=>{
      console.log('111',value)
    })
    p.then(value=>{
      console.log('222',value)
    })

image

而我们现在的callback只会保存最后一次的回调函数,前面的回调会全部被覆盖掉,因此将callback定为对象是有所欠缺的,因此,我们将callback改为数组,每当被指定回调时就将回调压入数组,这样所有回调都可以被保存。

class Promise {
  constructor(executor) {
    this.PromiseState = 'pending'
    this.PrmiseResult = null
    this.callback = [] //用于存储回调函数
    //resolve函数
    ······
    //reject函数
    ······
    //捕获throw跑出的错误
    ······
  }
  //添加then方法
  then(onResolve, onReject) {
    if (this.PromiseState == 'fulfilled') {
      onResolve(this.PrmiseResult);
    }
    if (this.PromiseState == 'rejected') {
      onReject(this.PrmiseResult);
    }
    //判断pending状态,如果此时状态还为pending则交给resolve和reject,在这两个函数调用的时候调用回调
    if (this.PromiseState == 'pending') {
      //保存回调函数
      this.callback.push = ({
        onResolve,
        onReject //这里使用了es6的键值同名,省略键的操作
      })
    }
  }
}

同样,resolve和reject函数也需要修改

const resolve = (data) => {
  //1.修改状态(promiseState)
  if (this.PromiseState != 'pending') return // 保证只修改一次
  this.PromiseState = 'fulfilled'
  //2.设置结果值(promiseResult)
  this.PrmiseResult = data
  //执行回调
  //使用数组和遍历来保证多次then回调都能执行
  if (this.callback.length != 0) {
    this.callback.forEach(x => {
      x.onResolve(data)
    })
  }
}

then方法的return结果返回

我们都知道,then方法返回的结果是个Promise对象,该对象成功与否通过内部onResolve 或者onReject 函数的return来决定,如果return返回的是非Promise实例对象,那么then返回成功的结果,如果返回的是Promise实例对象,那么then将根据Promise实例对象返回的结果成功与否来决定自身成功与否。

根据要求,首先我们要将then方法的返回结果改为Promise对象

then(onResolve, onReject) {
    //then方法将返回一个Promise对象
    return new Promise((resolve, reject) => {
      if (this.PromiseState == 'fulfilled') {
        onResolve(this.PrmiseResult);
      }
      if (this.PromiseState == 'rejected') {
        onReject(this.PrmiseResult);
      }
      //判断pending状态,如果此时状态还为pending则交给resolve和reject,在这两个函数调用的时候调用回调
      if (this.PromiseState == 'pending') {
        //保存回调函数
        this.callback.push = ({
          onResolve,
          onReject //这里使用了es6的键值同名,省略键的操作
        })
      }
    })
  }

接下来就是最关键的一步,由于then方法返回的Promise对象受到内部函数onResolveonReject return的影响,我们就要获取onResolve 或者onReject 的return结果然后进行判断

try {
  if (this.PromiseState == 'fulfilled') {
    //让then返回的结果给result,判断return的结果是否为Promise对象
    let result = onResolve(this.PrmiseResult);
    //是Promise对象,则执行then方法,根据result的Promise对象是否成功来决定这个Promise对象是否成功
    if (result instanceof Promise) {
      result.then(
        response => {
          resolve(response)
        },
        error => {
          reject(error)
        }
      )
    }
    else {
      //非Promise对象,直接给成功
      resolve(result)
    }
  }
} catch (error) {
  reject(error)
}

由于onResolve 函数内部有可能出现抛出异常的可能,当抛出异常时也会导致当前then返回的Promise对象的状态为失败,因此我们需要使用trycatch 来捕获异常并调用reject函数。

if判断rejected和上述代码片段同理

当然,我们目前修改了同步任务下的then方法返回Promise对象的状态改变,而异步任务并未改变:

if (this.PromiseState == 'pending') {
  //保存回调函数
  this.callback.push = ({
    onResolve,
    onReject //这里使用了es6的键值同名,省略键的操作
  })
}

此时如果是异步任务,由于没有调用resolve和reject两个中的任意一个函数,导致then方法return的Promise对象的状态还是pending,不符合我们的要求,因此,我们要对该处代码进行改变。

if (this.PromiseState == 'pending') {
  //保存回调函数
  this.callback.push = ({
    onResolve:()=>{
      try{
        //让then返回的结果给result,判断return的结果是否为Promise对象
        let result = onResolve(this.PrmiseResult);
        //是Promise对象,则执行then方法,根据result的Promise对象是否成功来决定这个Promise对象是否成功
        if (result instanceof Promise) {
          result.then(
            response => {
              resolve(response)
            },
            error => {
              reject(error)
            }
          )
        }
        else {
          //非Promise对象,直接给成功
          resolve(result)
        }
      } catch (error) {
            reject(error)
      }
    },
    onReject:()=>{
      ······
    }
  })
}

onReject 的修改与onResolve 同理

异常穿透与值传递

这里就比较简单了,我们只需要在then方法上判断onResolveonReject 是否是函数,如果不是函数补上函数即可。

  then(onResolve, onReject) {
    //异常穿透原理
    if (typeof onReject !== 'function') {
      onReject = reason => {
        throw reason
      }
    }
    if (typeof onResolve !== 'function') {
      onResolve = value => value
    }
    //then方法将返回一个Promise对象
    return new Promise((resolve, reject) => {
      ······
    })

小结

手写Promise函数的主体和主要思想目前都已完成,而剩下的几个方法的封装也比较简单,就不一一赘述,Promise函数主要难点在于函数之间相互嵌套,需要我们能够掌握每个参数之间的关系,每个函数调用的位置,如果能够理清思路那么就会发现Promise并没有想象中那么难。

以上是我学习手写Promise的一些理解和笔记,希望对你能有帮助

标签:resolve,函数,PromiseState,详解,onResolve,reject,Promise,手写
From: https://www.cnblogs.com/sakura-hfhj/p/16659538.html

相关文章