再也不怕面试官问你Promise了

504 阅读10分钟

hello!大家好,我是一名正在学习前端技术的大学生,欢迎大家关注我,一起探讨前端技术。

前言

promise是JavaScript里的一个强大的异步操作工具,经常出现在面试题中,并且异步请求函数axios也是基于promise实现的,由此可见promise的强大,在此之前我通过B站等网站观看过多个教程,也在掘金等博客上看过多篇文章,以下是我学习之后对promise的总结和理解。本文我们手写一个promise,深入理解promise。

正文

根据promise对象,先搭建一个基本框架

function MyPromise(excutor) {
//resovle函数
  function resolve(value){}
  
  //reject函数
  function reject(reason){}
  
  //调用执行器函数
  excutor(resolve, reject)
}

//添加.then方法
MyPromise.prototype.then = function (OnResolved, OnRejected) {}

在promise中有三个状态(pending,fulfilled,rejected)和结果值,所以应该在对象中声明变量存储状态和结果值,并且在调用resolve函数和reject函数时修改状态和结果。因为在调用resolve函数和reject函数时this的指向为window,所以应该在函数外面声明变量存储promise对象的this值。

  this.PromiseState = 'pending'
  this.PromiseResult = null
  const self = this
  
  function resolve(value) {
    //修改对象状态
    self.PromiseState = 'fulfilled'

    //设置对象结果值
    self.PromiseResult = value
  }
  
  //reject函数
  function reject(reason) {
    //修改对象状态
    self.PromiseState = 'rejected'

    //设置对象结果值
    self.PromiseResult = reason
  }

因为在promise实例中抛出异常后接收的应该是失败的promise实例对象,在MyPromise中抛出异常却收到控制台的报错,如图

屏幕截图 2024-05-08 172623.png

所以应该想到用try{}.catch{}来对抛出异常做出处理,由于执行抛出异常的函数是执行器函数,应该在调用执行器函数之前加try{}.catch{}判断是否为抛出异常。

  const p = new MyPromise((resolve, reject) => {
    throw "ERROR"
  })

  try {
    excutor(resolve, reject)
  } catch (e) {
    reject(e)
  }

在promise对象中因为修改状态和结果值只能修改一次,所以应该在修改状态和结果值之前判断状态值是否有被更改(即PromiseState是否为pending)。以下用resolve函数举例(reject函数同理):

  this.PromiseState = 'pending' 
  this.PromiseResult = null 
  function resolve(value) {
    //判断Promise对象状态是否被改变
    if (self.PromiseState !== 'pending') return
    
    //修改对象状态 
    self.PromiseState = 'fulfilled' 
    
    //设置对象结果值 
    self.PromiseResult = value
  }

在.then方法里成功才执行OnResolved;失败才执行OnRejected,所以要先判断是否成功。

  if (this.PromiseState === 'fulfilled') {
    OnResolved(this.PromiseResult)  //执行函数需要传递参数
  }
  if (this.PromiseState === 'rejected') {
    OnRejected(this.PromiseResult)
  }

这里能直接使用this的原因是调用.then方法的是promise实例对象,所以它的this指向为promise对象。

继续补充then方法,then方法返回的应该是一个promise对象,并且对抛出异常做出相应的处理(使用try{}catch{}包裹),由于只是单纯的调用OnResolved函数或OnRejected函数并没有改变Promise对象的状态(即状态仍为pending),所以应该声明变量接收OnResolved函数或OnRejected函数返回的值并做出判断(补充:使用instanceof判断,举例:A instanceof B,可以判断A属性是否在B的原型链上),如果为一个promise对象,则调用它本身的.then方法,如果不为promise对象,则直接调用resolve或reject函数修改promise的状态并返回结果值。

return new MyPromise((resolve, reject) => {
if (this.PromiseState === 'fulfilled') {
      //对抛出异常做出回应
      try {
        //return后状态没有被改变
        //获取回调函数的执行结果
        let result = OnResolved(this.PromiseResult)  

        //对执行结果进行判断
        if (result instanceof Promise) {
          //如果为Promise
          result.then(rev => {
            resolve(rev)
          }, rej => {
            reject(rej)
          })
        } else {
          resolve(result)
        }
      } catch (e) {
        reject(e)
      }
    }
    if (this.PromiseState === 'rejected') {
      OnRejected(this.PromiseResult)
    }
  })
}

if (this.PromiseState === 'rejected') {
      //对抛出异常做出回应
      try {
        //return后状态没有被改变
        //获取回调函数的执行结果
        let result = OnRejected(this.PromiseResult)  

        //对执行结果进行判断
        if (result instanceof Promise) {
          //如果为Promise
          result.then(rev => {
            resolve(rev)
          }, rej => {
            reject(rej)
          })
        } else {
          resolve(result)
        }
      } catch (e) {
        reject(e)
      }
    }

这里应该有人会问状态为rejected,返回的结果不是promise对象为什么要把状态改为fulfilled,因为它返回结果为undefined,所以调用.then方法后状态会修改为fulfilled。

异步任务

在异步任务中因为执行代码是从上往下,所以执行到then方法时由于没有拿到状态值而返回的promise对象状态为pending,所以要在then方法的pending做出判断,由于调用回调函数的为resolve函数和reject函数,应该将要执行的回调保存起来以便调用,由于可能会多次调用.then方法,所以应该声明数组来保存回调。

//在MyPromise中声明数组保存回调
this.callbacks = []

//在resolve函数和reject函数调用相应的回调

//resolve函数
self.callbacks.forEach(item => item.OnResolved(value))

//reject函数
self.callbacks.forEach(item => item.OnRejected(reason))

//then方法中加pending的判断
if (this.PromiseState === 'pending') {
      //因为调用回调函数的应该是resolve函数,所以在这里应该保存回调
      this.callbacks.push(
        {
          OnResolved,
          OnRejected
        }
      )
    }

异步任务中因为在pending状态下调用.then方法并没有调用resolve函数或reject函数导致promise对象状态并未改变(处于pending状态),所以应该在OnResolved和OnRejected函数中声明函数以便调用时可以改变promise状态,在调用函数前应该对抛出异常做出处理。

  OnResovled: function () {
            //对抛出异常做出处理
            try {
              //因为这里的this指向并不是promise对象,应该声明变量存储
              //接收回调返回的值判断是否为一个promise对象
              let result = OnResolved(self.PromiseResult)
              //判断
              if (result instanceof Promise) {
                result.then(rev => {
                  resolve(rev)
                }, rej => {
                  reject(rej)
                })
              } else {
                resolve(result)
              }
            } catch (e) {
              reject(e)
            }
          },
          OnRejectd: function () {
            //对抛出异常做出处理
            try {
              let result = OnRejectd(self.PromiseResult)
              //判断
              if (result instanceof Promise) {
                result.then(rev => {
                  resolve(rev)
                }, rej => {
                  reject(rej)
                })
              } else {
                resolve(result)
              }
            } catch (e) {
              reject(e)
            }
          }

添加catch方法

实现异常穿透和值传递

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

  p.then(value => {
    console.log(111)
  }).then(value => {
    console.log(222)
  }).then(value => {
    console.log(333)
  }).catch(
    console.warn('error')
  )

因为在.then方法中只有一个参数并没有状态为rejected的回调函数的参数,所以在进入.then方法后就会报错误OnRejectd is not a function,如图:

屏幕截图 2024-05-08 152942.png

所以在.then方法中应该进行回调函数的参数判断,如果它不是一个函数,就追加一个函数做为参数。

//判断回调函数参数
  if (typeof OnRejected !== 'function') {
    OnRejected = reason => {
      throw reason
    }
  }

值传递

//值传递
  const res = p.then(

  ).then(value => {
    console.log(222)
  }).then(value => {
    console.log(333)
  }).catch(
    console.warn('error')
  )
  console.log(res)

屏幕截图 2024-05-08 154807.png

原理相同,由于.then中没有状态值为resolved的回调函数参数,所以需要进行回调函数参数判断,如果不是一个function,则追加函数作为参数。

if (typeof OnResolved !== 'function') {
    OnResolved = value => value
  }

屏幕截图 2024-05-08 154934.png

添加.resolve方法和.reject方法

因为.resolve方法和.reject方法是属于promise函数对象的,所以不用在prototype里面追加函数。

//添加promise.resolve方法,.resolve是属于promise函数对象的,
MyPromise.resolve = function (value) {
  return new MyPromise((resolve, reject) => {
    //判断value是否为promise对象
    if (value instanceof Promise) {
      value.then(rev => {
        resolve(rev)
      }, rej => {
        reject(rej)
      })
    } else {
      resolve(value)
    }
  })
}

//添加promise.reject方法
MyPromise.reject = function (reason) {
  return new MyPromise((resolve, reject) => {
    reject(reason)
  })
}

添加.all方法和.race方法

.all方法:接收一个promise数组,当全部都为成功时返回一个成功值所组成的数组,如图:

屏幕截图 2024-05-08 175314.png

如果其中有一个为失败是,则返回该失败的promise对象,如图:

屏幕截图 2024-05-08 175436.png

屏幕截图 2024-05-08 175417.png

.race方法:接收一个promise数组,谁先被调用返回谁的结果值 测试函数如图:

屏幕截图 2024-05-08 175707.png

测试结果如图:

屏幕截图 2024-05-08 175756.png

//添加pomise.all方法(当全部都为成功是返回成功值所组成的数组,其中一个失败则返回一个失败的promise)
MyPromise.all = function (promises) {
  //声明数组存储返回的PromiseResult值
  let arr = []
  return new MyPromise((resolve, reject) => {
    //遍历
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(rev => {
        arr[i] = rev
        if (i == promises.length - 1) resolve(arr)
      }, rej => {
        reject(rej)
      })
    }
  })
}

//添加promise.race方法(谁先被调用返回谁的结果)
MyPromise.race = function (promises) {
  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(rev => {
        resolve(rev)
      }, rej => {
        reject(rej)
      })
    }
  })
}

完整代码如下:

function MyPromise(excutor) {
  //声明变量存储状态和结果
  this.PromiseState = 'pending'
  this.PromiseResult = null

  //声明变量保存回调函数
  //因为多次调用then方法时,只用对象无法全部保存执行的回调
  this.callbacks = []

  //保存实例对象的this值
  //在函数resolve里的this指向为window而不是实例对象
  let self = this
  //resovle函数
  function resolve(value) {
    //因为修改状态只能修改一次,所以应该提前判断保存的状态值是否有被修改过即(状态值是否为pending)
    if (self.PromiseState !== 'pending') return
    //修改对象状态
    self.PromiseState = 'fulfilled'

    //设置对象结果值
    self.PromiseResult = value

    //调用成功的回调函数
    self.callbacks.forEach(item => item.OnResolved(value))
  }

  //reject函数
  function reject(reason) {
    if (self.PromiseState !== 'pending') return
    //修改对象状态
    self.PromiseState = 'rejected'

    //设置对象结果值
    self.PromiseResult = reason

    //调用成功的回调函数
    self.callbacks.forEach(item => item.OnRejected(reason))
  }

  //调用执行器函数
  //因为执行抛出错误异常的函数是执行器函数,所以应该在调用执行器函数的时候加上try catch
  try {
    excutor(resolve, reject)
  } catch (e) {
    reject(e)
  }

}

//添加then方法
MyPromise.prototype.then = function (OnResolved, OnRejected) {
  //判断回调函数参数
  if (typeof OnRejected !== 'function') {
    OnRejected = reason => {
      throw reason
    }
  }

  if (typeof OnResolved !== 'function') {
    OnResolved = value => value
  }

  //then方法应该返回的是一个promise对象
  return new MyPromise((resolve, reject) => {
    //声明变量存储this值
    let self = this

    //执行回调
    //因为成功才执行OnResolved,失败才执行Onrejected,所以要提前进行判断
    //因为调用then方法的是promise对象,所以this指向也为promise
    if (this.PromiseState === 'fulfilled') {
      //对抛出异常做出回应
      try {
        //return后状态没有被改变
        //获取回调函数的执行结果
        let result = OnResolved(this.PromiseResult)  //因为执行函数需要有传递参数
        //对执行结果进行判断
        if (result instanceof Promise) {
          //如果为Promise
          result.then(rev => {
            resolve(rev)
          }, rej => {
            reject(rej)
          })
        } else {
          resolve(result)
        }
      } catch (e) {
        reject(e)
      }
    }
    if (this.PromiseState === 'rejected') {
      //对抛出异常做出回应
      try {
        //return后状态没有被改变
        //获取回调函数的执行结果
        let result = OnRejected(this.PromiseResult)  //因为执行函数需要有传递参数
        //对执行结果进行判断
        if (result instanceof Promise) {
          //如果为Promise
          result.then(rev => {
            resolve(rev)
          }, rej => {
            reject(rej)
          })
        } else {
          resolve(result)
        }
      } catch (e) {
        reject(e)
      }
    }

    //因为在异步执行中,因为resolve函数没有调用,状态没有改变,所以then方法中不能正常输出
    //所以应该加个pending的判断条件
    if (this.PromiseState === 'pending') {
      //因为调用回调函数的应该是resolve函数,所以在这里应该保存回调
      this.callbacks.push(
        {
          //异步任务中,pending状态下没有调用resolve函数或reject函数
          //所以并未改变promise状态,当改变状态后会执行该函数
          OnResolved: function () {
            //对抛出异常做出处理
            try {
              //因为这里的this指向并不是promise对象,应该声明变量存储
              //接收回调返回的值判断是否为一个promise对象
              let result = OnResolved(self.PromiseResult)
              //判断
              if (result instanceof Promise) {
                result.then(rev => {
                  resolve(rev)
                }, rej => {
                  reject(rej)
                })
              } else {
                resolve(result)
              }
            } catch (e) {
              reject(e)
            }
          },
          OnRejected: function () {
            //对抛出异常做出处理
            try {
              let result = OnRejected(self.PromiseResult)
              //判断
              if (result instanceof Promise) {
                result.then(rev => {
                  resolve(rev)
                }, rej => {
                  reject(rej)
                })
              } else {
                resolve(result)
              }
            } catch (e) {
              reject(e)
            }
          }
        }
      )
    }
  })
}

//添加catch方法
MyPromise.prototype.catch = function (OnRejected) {
  //因为catch方法只是对错误做出响应所以可以直接使用.then方法,并且第一个参数为undefined
  return this.then(undefined, OnRejected)
}

//添加promise.resolve方法,.resovle是属于promise函数对象的,
MyPromise.resolve = function (value) {
  return new MyPromise((resolve, reject) => {
    //判断value是否为promise对象
    if (value instanceof Promise) {
      value.then(rev => {
        resolve(rev)
      }, rej => {
        reject(rej)
      })
    } else {
      resolve(value)
    }
  })
}

//添加promise.reject方法
MyPromise.reject = function (reason) {
  return new MyPromise((resolve, reject) => {
    reject(reason)
  })
}

//添加pomise.all方法(当全部都为成功是返回成功值所组成的数组,其中一个失败则返回一个失败的promise)
MyPromise.all = function (promises) {
  //声明数组存储返回的PromiseResult值
  let arr = []
  return new MyPromise((resolve, reject) => {
    //遍历
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(rev => {
        arr[i] = rev
        if (i == promises.length - 1) resolve(arr)
      }, rej => {
        reject(rej)
      })
    }
  })
}

//添加promise.race方法(谁先被调用返回谁的结果)
MyPromise.race = function (promises) {
  return new MyPromise((resolve, reject) => {
    for (let i = 0; i < promises.length; i++) {
      promises[i].then(rev => {
        resolve(rev)
      }, rej => {
        reject(rej)
      })
    }
  })
}

结语

小伙伴们,以上是我学习后对promise的理解,还请多在评论区留言,大家相互讨论相互学习。