手拿把掐学习Promise

lxf2023-05-05 01:03:13

Promise

Promise对于前端从业者或者说JavaScript开发者来讲都是不会陌生的一个关键对象,他诞生于es6的标准中,问世之后便备受各路程序大佬,极客所青睐,因为他的出现解决了让无数从业者头疼的的回调地狱问题,同时async和await的语法糖能让js用同步书写代码方式来书写异步代码让代码更加简洁更加优雅。话不多说跟随笔者一起来学习掌握这个关键的对象吧。

Promise基本特性和使用

首先,Promise构造时会返回两个回调函数resolve和reject,他们都可以接收参数,但是resolve会调用成功函数回调将Promise对象的状态置为fulfilled,而reject调用的为失败函数会将Promise对象的状态置为rejected。注意Promise的状态是不可逆的当状态改变之后无法再进行更改。

基础使用

  new Promise((resolve, reject) => {
    resolve(1)
    //调用不生效
    reject(2)
  }).then(res => {
  //成功回调
    console.log(res, 'res')
  }, err => {
  //失败回调
    console.log(err, 'err')
  })

链式多次调用

new Promise((resolve, reject) => {
    resolve(1000) // 输出 状态:fail 值:200
   }).then(res => new Promise((resolve, reject) => reject(2 * res)), err => new Promise((resolve, reject) => resolve(3 * err)))
    .then(res => console.log('success', res), err => console.log('fail', err))

回调地狱

回调地狱简单解释就是需要得到上个异步函数执行结果返回之后才能进行下一步操作的业务场景,这里通过定时器来模拟ajax请求处理过程

 let a = 0
  setTimeout(() => {
    a = 2
    setTimeout(() => {
      a = 3
      setTimeout(() => {
        a = 4
        setTimeout(() => {
        //四秒后输出4
          console.log(a)
        }, 1000)
      }, 1000)
    }, 1000)
  }, 1000)

解决办法

  let a = 0
  new Promise((resolve, reject) => {
    setTimeout(() => {
      a = 2
      resolve(a)
    }, 1000)
  }).then(res => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        a = 3
        resolve(a)
      }, 1000)
    })
  }).then(res => {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        a = 4
        resolve(a)
      }, 1000)
    })
  }).then(res => {
    console.log(a)
  })
//三秒后输出4
//async 和awite解决回调地狱会更加优雅可读性更好
async function fn() {
    let a = 0
    a = await new Promise((resolve, reject) => {
      setTimeout(() => {

        resolve(2)
      }, 1000)
    })
    a = await new Promise((resolve, reject) => {
      setTimeout(() => {

        resolve(3)
      }, 1000)
    })
    a = await new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(4)
      }, 1000)
    })
    console.log(a)
  }
  fn()
  //三秒后输出4

手写Promise

笔者以为为了加深对某个功能的掌握程度手写一遍最为有效,而且手写Promise也是面试中高频考题之一。

 //  1.执⾏了resolve,Promise状态会变成fulfilled; 
  //  2. 执⾏了reject,Promise状态会变成rejected;
  //  3. Promise状态不可逆,第⼀次成功就永久为fulfilled,第⼀次失败就永远状态为rejected; 
  //  4. Promise中有throw的话,就相当于执⾏了reject;
  //  5.实现then并接受两个回调
  //  6.resolve=>success,reject=>err
  //  7.如resolve或reject在定时器⾥,则定时器结束后再执⾏then;
  //  8.实现then的链式调用
  class myPromise {
    constructor(execonstr) {
      // 需要初始化一个状态
      this.initState()
      // 初始化resolve,reject
      this.initCallback()
      //如果执行错误,抛出异常并保证程序不挂
      try {
        execonstr(this.resolve, this.reject)
      } catch (e) {
        this.reject(e)

      }
    }
    initState() {
      // 初始化状态值
      this.PromiseState = 'pending'
      //初始化返回值
      this.PromiseResult = null
      //存储成功回调
      this.resCallblackArray = []
      // 储存失败回调
      this.errCallblackArray = []

    }
    initCallback() {
      this.resolve = this.resolve.bind(this)
      this.reject = this.reject.bind(this)
    }
    resolve(argum) {
      // console.log(argum, 'resolve')
      // 状态更改一次之后不能在此更改
      if (this.PromiseState !== 'pending') return
      this.PromiseResult = argum
      this.PromiseState = 'fulfilled'
      while (this.resCallblackArray.length) {
        this.resCallblackArray.shift()(this.PromiseResult)
      }
    }
    reject(argum) {
      // console.log(argum, 'reject')
      if (this.PromiseState !== 'pending') return
      this.PromiseResult = argum
      this.PromiseState = 'rejected'
      while (this.errCallblackArray.length) {
        this.errCallblackArray.shift()(this.PromiseResult)
      }
    }
    then(res, err) {

      res = typeof res == 'function' ? res : (PromiseResult) => PromiseResult
      err = typeof err == 'function' ? err : (PromiseResult) => new Error(不能返回自身)
      // 书写then的链式调用首先是返回一个promise对象
      var promiseObject = new myPromise((resolve, reject) => {
        setTimeout(() => {
          let resolvePromise = (cb) => {
            // 防止报错
            try {
              const x = cb(this.PromiseResult)
              // 防止进入死循环,不能返回自身
              if (x === promiseObject & x) {
                throw new Error('不能返回自身')
              }
              // 如果后续是promise对象就根据其状态调用resolv或者reject
              if (x instanceof myPromise) {
                x.then(resolve, reject)
              } else {
                // 普通返回就直接resolve
                resolve(x)
              }
            } catch (error) {
              reject(error)
              throw new Error(error)

            }

          }
          if (this.PromiseState == 'fulfilled') {
            resolvePromise(res)
          } else if (this.PromiseState == 'rejected') {
            resolvePromise(err)
          } else if (this.PromiseState == 'pending') {
            // 讲方法实例进行绑定
            this.resCallblackArray.push(resolvePromise.bind(this, res))
            this.errCallblackArray.push(resolvePromise.bind(this, err))
          }
        })
      })
      return promiseObject
    }
  }
  
  

测试代码

new myPromise((resolve, reject) => {
    resolve(1000) // 输出 状态:fail 值:200
   }).then(res => new myPromise((resolve, reject) => reject(2 * res)), err => new myPromise((resolve, reject) => resolve(3 * err)))
    .then(res => console.log('success', res), err => console.log('fail', err))

手写Promise方法

Promise.all(list),all方法是Promise中使用频率比较多的方法,接受一个数组,数组中存放Promise对象或其他数据类型,若Promise全部返回成功,则返回一个成功值的数组,若返回中有一个失败,直接走失败流程
公共参数定义

//定义公共参数
let promiseList = [
    new Promise((resolve, reject) => {
      resolve(1)
    }),
    new Promise((resolve, reject) => {
      resolve(2)
    }),
    new Promise((resolve, reject) => {
      resolve(3)
    }),
    4,
    new Promise((resolve, reject) => {
      reject(5)
    }),
  ]
  let promiseList2 = [
    new Promise((resolve, reject) => {
      reject(1)
    }),
    new Promise((resolve, reject) => {
      reject(2)
    }),
    new Promise((resolve, reject) => {
      reject(3)
    }),
    // 4,
    new Promise((resolve, reject) => {
      reject(5)
    }),
  ]
 // 全部成功返回成功结果数组
  // 如果失败一个就结束返回失败结果
  function all(argmList) {
    let resletList = []
    let cout = 0
    return new Promise((resolve, reject) => {
      let addData = (reslet, index) => {
        resletList[index] = reslet
        cout++
        if (cout == argmList.length) {
          resolve(resletList)
        }
      }
      // 1.如果全部成功则返回成功的数组,
      // 2.若有一个失败则返回失败的数组
      // 如果
      argmList.forEach((promise, index) => {
        if (promise instanceof myPromise) {
          // 成功就收集起来
          promise.then(res => {
            addData(res, index)
          }, err => {
            // 失败直接抛出
            reject(err)
          })

        } else {
          addData(promise, index)

        }
      })
    })
  }

Promise.race(list),race方法也是接受一个数组,数组中存放Promise对象或其他数据类型,若Promise有一个处理完成,则进入对象处理结果(成功或者失败)结束流程

 // 看谁跑的快,先执行完就返回
  function race(argmList) {

    return new Promise((resolve, reject) => {

      argmList.forEach((promise, index) => {

        if (promise instanceof myPromise) {
          promise.then(res => {
            resolve(res)
          }, err => {
            // 失败直接抛出
            reject(err)
          })
        } else {
          resolve(promise)
        }
      })
    })
  }

Promise.allSettled(list)会等待所有结果执行结束返回处理结果的数组,并且返回处理状态。

 // 会返回所有结果的数组
  function allSettled(argmList) {
    let resletList = []
    let cout = 0
    return new myPromise((resolve, reject) => {
      let addData = (result, states, index) => {
        resletList[index] = {
          states,
          result,
        }
        cout++
        if (cout == argmList.length) {
          resolve(resletList)
        }
      }
      argmList.forEach((promise, index) => {
        if (promise instanceof myPromise) {
          // 收集结果
          promise.then(res => {
            addData(res, 'fulfilled', index)
          }, err => {
            
            addData(err, 'rejected', index)
          })

        } else {
          addData(promise, 'fulfilled', index)


        }
      })
    })
  }

Promise.all(list)和Promise.any(list)功能刚好相反,如果全部失败则返回失败结果的组数,若有一个成功则返回成功

  // 和all刚好相反成功一个就结束如果全部失败就返回失败的数组
  function any(argmList) {
    let resletList = []
    let cout = 0
    return new myPromise((resolve, reject) => {
      let addData = (reslet, index) => {
        resletList[index] = reslet
        cout++
        if (cout == argmList.length) {
          reject(resletList)
        }
      }
      argmList.forEach((promise, index) => {
        if (promise instanceof myPromise) {
          // 成功直接抛出
          promise.then(res => {
            resolve(res)
          }, err => {
            // 失败收集结果
            addData(err, index)
          })
        } else {
          resolve(res)
        }
      })
    })
  }

generator

generator函数可能在实战中用的比较少所以不常见,他和普通函数不同之处就是定义时前面加一个*,并且只有在generator函数中才能使用yield关键字,相当于在函数执行中的暂停点,通过next函数控制函数进程,同时next方法执行之后会返回一个对象,对象中有value和done两个属性,value是暂停点后接收到的值,done为布尔值,代表后方是否还有暂停点,没有为true,有为false

function* gFn() {
    let num = yield 1
    console.log(num)//10
    let num2 = yield 2
     console.log(num2)//20
    let num3 = yield 3
     console.log(num3)//30
    return num3

  }
  let g = gFn()
  console.log(g.next())
  //{value:1,done:false}
  console.log(g.next(10))
   //{value:2,done:false}
  console.log(g.next(20))
   //{value:3,done:false}
  console.log(g.next(30))
   //{value:30,done:true}

这里提及generator函数知识点是因为async和await函数手写实现时候会涉及所以提前在提醒一下

async和await

async和await是es7中新增的Promise语法糖,他可以以同步格式来书写异步函数,可以让代码更加优雅,在node开发中使用较多,前端开发中频率一般。

async function fn() {
    let a = 0
    a = await new Promise((resolve, reject) => {
      setTimeout(() => {

        resolve(2)
      }, 1000)
    })
    a = await new Promise((resolve, reject) => {
      setTimeout(() => {

        resolve(3)
      }, 1000)
    })
    a = await new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(4)
      }, 1000)
    })
    console.log(a)
  }
  fn()
  //三秒后输出4

需要注意的是,函数用async包裹之后会返回一个Promise对象,所以这个本质上等待的是Promise对象,如果await返回的不是Promise对象,会直相当于接执行resolve,不会进行阻塞。
手写实现

 // 模仿书写asyc
  function gPromise(value) {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(value * 3)
      }, 1000)
    })
  }
  function* gFn() {
    let num1 = yield gPromise(1)
    let num2 = yield gPromise(num1)
    let num3 = yield gPromise(num2)
    return num3
  }
  // let g = gFn()
  function ansycFu(gFn2) {
    return function () {
      let g = gFn2.call(this.arguments)
      return new Promise((resolve, reject) => {
        let go = function (key, arg) {
          let { value, done } = g[key](arg)
          // let res = g[key](arg)
          // console.log(res)
          //  判断后续时候还有调用
          if (done) {
            return resolve(value)
          } else {
            value.then(res => {
              go('next', res)
            },
              err => {
                go('next', err)
              })
          }
        }
        go('next')
      })
    }
  }
  ansycFu(gFn)().then(res => {
    console.log(res)
  })

后话

 本篇文章到此就已经结束了,本人在撰写时结合了自己工作时的经历,但是可能受限于资历尚浅可能会有些见解不能理解到位,如有不同见解欢迎在评论区一起讨论学习。感谢各位

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!