由浅入深,从掌握Promise的基本使用到手写Promise

博客 分享
0 281
优雅殿下
优雅殿下 2022-04-03 18:57:29
悬赏:0 积分 收藏

由浅入深,从掌握Promise的基本使用到手写Promise

由浅入深,从掌握Promise的基本使用到手写Promise

前言

在ES6之前,对于一些异步任务的处理始终没有很好的方案可以解决,处理异步的方案可谓是十分混乱,在业务需求下异步请求的套用,就形成了回调地狱,严重影响代码的阅读性。而Promise的出现,给我们统一了规范,解决了之前处理异步任务的许多痛点,并且它友好的使用方式,使之成为了JavaScript一大重点,同时也是面试的高频问点,下面就一起来全面认识一下Promise吧。

1.什么是Promise?

如果我们想在一个异步请求之后,拿到请求的结果,在ES6之前我们可以怎么做呢?

比如,给定一个请求地址,希望拿到它请求成功或者失败的结果:

  • 可以通过分别设置成功和失败的两个回调
  • 当请求成功后调用成功的回调,将成功的结果传递过去;
  • 当请求失败后调用失败的回调,将失败的结果传递过去;
function request(url, successCb, failCb) {  setTimeout(function() {    if (url === '/aaa/bbb') { // 请求成功      let res = [1, 2, 3]      successCb(res)    } else { // 请求失败      let err = 'err message'      failCb(err)    }  })}// 调用方式,从回调中拿结果request('/aaa/bbb', function(res) {  console.log(res)}, function(err) {  console.log(err)})

将上面的情况使用Promise来实现一下:

  • Promise是一个类,通过new调用,可以给予调用者一个承诺;
  • 通过new创建Promise对象时,需要传入一个回调函数,这个回调函数称之为executor,executor接收两个参数resolve和reject;
  • 传入的回调会被立即执行,当调用resolve函数时,会去执行Promise对象的then方法中传入的成功回调;
  • 当调用reject函数时,会去执行Promise对象的then方法中传入的失败回调函数,并且请求后的结果可以通过参数传递过去;
function request(url) {  return new Promise((resolve, reject) => {    setTimeout(() => {      if (url === '/aaa/bbb') {        let res = [1, 2, 3]        resolve(res) // 请求成功调用resolve      } else {        let err = 'err message'        reject(err) // 请求失败调用reject      }    })  })}const p = request('/aaa/bbb')p.then(res => {  console.log(res) // 拿到resolve传递过来的值}, err => {  console.log(err) // 拿到reject传递过来的值})

2.Promise的三种状态

为什么Promise能够将请求的结果准确的传递到then中的回调函数中,因为Promise其核心就用三种状态来进行管控。

  • 待定状态(pending):Promise的初始状态;
  • 已兑现(resolved、fulfilled):操作成功,如执行resolve时就变为该状态;
  • 已拒绝(rejected):操作失败,如执行reject时就变为该状态;

通过上面的案例,可以在浏览器中查看Promise分别在执行resolve和reject后的打印结果和Promise当时处于的状态:

  • resolve和reject都没执行:

  • 执行resolve,请求成功:

  • 执行reject,请求失败:

注意:在后续的对Promise的讲述过程中,都需要带着Promise的状态去理解。

3.executor

executor是在创建Promise是需要传入的一个回调函数,这个回调函数会被立即执行,并且传入两个参数,分别就是resolve和reject。

new Promise((resolve, reject) => {  console.log('我是executor中的代码,我会被立即执行~')})

通常我们会在executor中确定Promise的状态,而且状态一旦被确定下来,Promise的状态就会被锁死,即Promise的状态一旦修改,就不能再次更改了

  • 当调用resolve,如果resolve传入的值不是一个Promise(即传入的值为一个普通值),Promise的状态就会立即变成fulfilled;
  • 但是,如果在resolve后接着调用reject,是不会有任何的效果的,因为reject已经无法改变Promise的结果了;

4.resolve的参数

上面聊到了resolve需要传入一个普通值,Promise的状态才会被立即锁定为fulfilled,那么如果传递的不是普通值呢?一般resolve传递以下三类值,会有不同的表现效果。

  • 传值一:resolve传入一个普通值或普通对象,那么这个值会作为then中第一个回调的参数;

    const p = new Promise((resolve, reject) => {  resolve(123)})p.then(res => {  console.log(res) // 123})
  • 传值二:resolve传入一个Promise,那么这个传入的Promise会决定原来Promise的状态;

    • 传入的Promise调用的是resolve;

      const newP = new Promise((resolve, reject) => {  resolve(123)})const p = new Promise((resolve, reject) => {  resolve(newP)})p.then(res => {  console.log(res) // 123}, err => {  console.log(err)})
    • 传入的Promise调用的是reject;

      const newP = new Promise((resolve, reject) => {  reject('err message')})const p = new Promise((resolve, reject) => {  resolve(newP)})p.then(res => {  console.log(res)}, err => {  console.log(err) // err message})
  • 传值三:resolve传入一个特殊对象,该对象中实现了then方法,那么Promise的状态就是对象中then方法执行后的结果来决定的;

    • then中执行了resolve;

      const obj = {  then: function(resolve, reject) {    resolve(123)  }}const p = new Promise((resolve, reject) => {  resolve(obj)})p.then(res => {  console.log(res) // 123}, err => {  console.log(err)})
    • then中执行了reject;

      const obj = {  then: function(resolve, reject) {    reject('err message')  }}const p = new Promise((resolve, reject) => {  resolve(obj)})p.then(res => {  console.log(res)}, err => {  console.log(err) // err message})

5.Promise相关实例方法

Promise的实例方法,就是可以通过其实例对象进行调用的方法。

5.1.then方法

then方法是Promise实例对象上的一个方法:Promise.prototype.then

(1)then方法接收两个参数

  • 状态变成fulfilled的回调函数;
  • 状态变成rejected的回调函数;
promise.then(res => {  console.log('状态变成fulfilled回调')}, err => {  console.log('状态变成rejected回调')})

(2)then方法多次调用

  • 一个Promise的then方法是可以被多次调用的,每次调用都可以传入对应的fulfilled回调;
  • 当Promise的状态变成fulfilled的时候,这些回调函数都会被执行;
  • 反之,当Promise的状态变成rejected,所有then中传入的rejected回调都会被执行;
const p = new Promise((resolve, reject) => {  resolve('aaa')})p.then(res => {  console.log(res) // aaa})p.then(res => {  console.log(res) // aaa})p.then(res => {  console.log(res) // aaa})

(3)then方法中的返回值

then调用本身是有返回值的,并且它的返回值是一个Promise,所以then可以进行链式调用,但是then方法调用的返回值的状态是什么呢?主要是由其返回值决定的。

  • 当then方法中的回调在执行时处于pending状态;

  • 当then方法中的回调返回一个结果时处于fulfilled状态,并且会将结果作为resolve的参数;

    • 返回一个普通的值:这个普通的值会被作为一个新Promise的resolve中的值

      p.then(res => {  return 123  // 相当于:  /*    return new Promise((resolve, reject) => {      resolve(123)    })  */}).then(res => {  console.log(res) // 123})
    • 返回一个实现了then方法的对象:

      p.then(res => {  const obj = {    then: function(resolve, reject) {      resolve('abc')    }  }  return obj  // 相当于:  /*    return new Promise((resolve, reject) => {      resolve(obj.then)    })  */}).then(res => {  console.log(res) // abc})
    • 返回一个Promise:

      p.then(res => {  const newP = new Promise((resolve, reject) => {    resolve(123)  })  return newP  // 相当于:  /*    const newP = new Promise((resolve, reject) => {      resolve(123)    })    return new Promise((resolve, reject) => {      resolve(newP)    })  */}).then(res => {  console.log(res) // 123})
  • 当then方法执行时抛出一个异常,就处于rejected状态,同样,Promise的executor在执行的时候抛出异常,Promise对应的状态也会变成rejected;

    const p = new Promise((resolve, reject) => {  throw new Error('err message')})p.then(res => {  console.log(res)}, err => {  console.log(err) // Error: err message  return new Error('then err message')}).then(res => {  console.log(res)}, err => {  console.log(err) // Error: then err message})

5.2.catch方法

catch方法是Promise实例对象上的一个方法:Promise.prototype.catch

(1)catch方法可多次调用

  • 一个Promise的catch方法也是可以被多次调用的,每次调用都可以传入对应的reject回调;
  • 当Promise的状态变成rejected的时候,这些回调就都会执行;
  • catch方法的效果和then方法的第二个回调函数差不多,可用于替代then方法的第二个回调;
const p = new Promise((resolve, reject) => {  reject('err message')})p.catch(err => {  console.log(err) // err message})p.catch(err => {  console.log(err) // err message})p.catch(err => {  console.log(err) // err message})

(2)catch方法的返回值

  • catch方法的执行返回的也是一个Promise对象,使用catch后面可以继续调用then方法或者catch方法;

  • 如果在catch后面调用then方法,会进入到then方法的fulfilled回调函数中,因为catch返回的Promise默认是fulfilled;

    p.catch(err => {  return 'catch return value'}).then(res => {  console.log(res) // catch return value})
  • 如果catch后续又调用了catch,那么可以抛出一个异常,就会进入后面的catch回调中;

    p.catch(err => {  throw new Error('catch err message')}).catch(err => {  console.log(err) // Error: catch err message})

(3)catch的作用

  • catch主要是用于捕获异常的,当executor抛出异常是,可以通过catch进行处理;

  • 注意:当Promise的executor执行reject或者抛出异常,后续必须要有捕获异常的处理,如下代码,虽然都调用了then方法,接着后续又调用了catch方法,但是then和catch是两次独立的调用,两次调用并没有联系,所以就被认定为没有处理异常。

    const p = new Promise((resolve, reject) => {  reject('err message')})p.then(res => {  console.log(res)})p.catch(err => {  console.log(err)})

  • 正确处理的方法为:

    // 方法一:p.then(res => {  console.log(res)}).catch(err => {  console.log(err)})// 方法二:p.then(res => {  console.log(res)}, err => {  console.log(err)})

5.3.finally方法

finally方法是Promise实例对象上的一个方法:Promise.prototype.finally

  • finally是在ES9中新增的,无论Promise的状态变成fulfilled还是rejected,最终都会执行finally中的回调;
  • 注意finally是不接收参数的,因为它必定执行;
const p = new Promise((resolve, reject) => {  resolve(123)})p.then(res => {  console.log(res) // 123}).catch(err => {  console.log(err)}).finally(() => {  console.log('finally code') // finally code})

6.Promise相关类方法

Promise的类方法,就是直接通过Promise进行调用。

6.1.resolve方法

resolve方法具体有什么用呢?当我们希望将一个值转成Promise来使用,就可以通过直接调用resolve方法来实现,其效果就相当于在new一个Promise时在executor中执行了resolve方法。

resolve传入的参数类型:

  • 参数为一个普通的值;

    const p = Promise.resolve('aaaa')// 相当于:/*  const p = new Promise((resolve, reject) => {    resolve('aaaa')  })*/console.log(p)

  • 参数为一个实现了then方法的对象;

    const p = Promise.resolve({  then: function(resolve, reject) {    resolve('aaaa')  }})// 相当于:/*  const p = new Promise((resolve, reject) => {    resolve({      then: function(resolve, reject) {        resolve('aaaa')      }    })  })*/console.log(p)

  • 参数为一个Promise;

    const p = Promise.resolve(new Promise((resolve, reject) => {  resolve('abc')}))// 相当于:/*  const p = new Promise((resolve, reject) => {    resolve(new Promise((resolve, reject) => {      resolve('abc')    }))  })*/console.log(p)

6.2.reject方法

reject方法和resolve的用法一致,只不过是将可以得到一个状态为rejected的Promise对象,并且reject不过传入的是什么参数,都会原封不动作为rejected状态传递到catch中。

// 1.传入普通值const p1 = Promise.reject(123)p1.then(res => {  console.log(res)}).catch(err => {  console.log('err:', err)})// 2.传入实现then方法对象const p2 = Promise.reject({  then: function(resolve, reject) {    resolve('aaaa')  }})p2.then(res => {  console.log(res)}).catch(err => {  console.log('err:', err)})// 3.传入Promiseconst p3 = Promise.reject(new Promise((resolve, reject) => {  resolve('aaaa') }))p3.then(res => {  console.log(res)}).catch(err => {  console.log('err:', err)})

6.3.all方法

all方法可以接收由多个Promise对象组成的数组(准确来说是可接收一个可迭代对象),all方法调用返回的Promise状态,由所有Promise对象共同决定。

  • 当传入的所有Promise对象的状态都为fulfilled是,all方法返回的Promise状态就为fulfilled,并且会将所有Promise对象的返回值组成一个数组

    const p1 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(111)  }, 1000)})const p2 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(222)  }, 2000)})const p3 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(333)  }, 3000)})Promise.all([p1, p2, p3]).then(res => {  console.log('res:', res) // res: [ 111, 222, 333 ]}).catch(err => {  console.log('err:', err)})
  • 当传入的Promise有一个变成了rejected状态,那么就会获取第一个变成rejected状态的返回值作为all方法返回的Promise状态;

    const p1 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(111)  }, 1000)})const p2 = new Promise((resolve, reject) => {  setTimeout(() => {    reject('err message')  }, 2000)})const p3 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(333)  }, 3000)})Promise.all([p1, p2, p3]).then(res => {  console.log('res:', res)}).catch(err => {  console.log('err:', err) // err: err message})

6.4.allSettled方法

相比于all方法,allSettled方法不管传入的Promise对象的状态是fulfilled还是rejected,最终都会讲结果返回,并且返回的结果是一个数组,数组中存放着每一个Promise对应的状态status和对应的值value。

const p1 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(111)  }, 1000)})const p2 = new Promise((resolve, reject) => {  setTimeout(() => {    reject('err message')  }, 2000)})const p3 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(333)  }, 3000)})Promise.allSettled([p1, p2, p3]).then(res => {  console.log('res:', res)}).catch(err => {  console.log('err:', err)})

6.5.race方法

race翻译为竞争,顾名思义哪一个Promise对象最先返回结果,就使用最先返回结果的Promise状态。

一下代码是p1最先有结果的,p1中执行的是resolve,所以返回的状态为fulfilled:

const p1 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(111)  }, 1000)})const p2 = new Promise((resolve, reject) => {  setTimeout(() => {    reject('err message')  }, 2000)})const p3 = new Promise((resolve, reject) => {  setTimeout(() => {    resolve(333)  }, 3000)})Promise.race([p1, p2, p3]).then(res => {  console.log('res:', res) // res: 111}).catch(err => {  console.log('err:', err)})

6.6.any方法

any方法是ES12中新增的方法,与race是类似的,any方法会等到有一个fulfilled状态的Promise,才会决定any调用返回新Promise的状态(也就是说any一定会等到有一个Promise状态为fullfilled)。

那么,如果所有的Promise对象的状态都变为了rejected呢?最终就会报一个AggregateError错误,如果想拿到所有的rejected状态的返回值,可以通过在捕获异常回调参数中的errors获取:

const p1 = new Promise((resolve, reject) => {  setTimeout(() => {    reject('err message1')  }, 1000)})const p2 = new Promise((resolve, reject) => {  setTimeout(() => {    reject('err message2')  }, 2000)})const p3 = new Promise((resolve, reject) => {  setTimeout(() => {    reject('err message3')  }, 3000)})Promise.any([p1, p2, p3]).then(res => {  console.log('res:', res)}).catch(err => {  console.log(err)  console.log(err.errors)})

注意:any方法是ES12新增的,node版本过低的话是会报错找不到any方法的,可以在浏览器中测试。

7.手写Promise

掌握了以上Promise的用法,那么就一步步来实现一下Promise吧。

7.1.executor的实现

  • 创建一个类,这个类可接收一个executor函数;
  • executor函数需传入两个函数resolve和reject,并且executor是需要立即执行的;
  • 创建三个常量用于管理Promise的三种状态;
  • 一旦Promise的状态改变就不能再次被修改
  • 还需将传入resolve和reject的参数值进行保存,便于后续then的使用;
// 定义Promise的三种状态常量const PENDING_STATUS = 'pending'const FULFILLED_STATUS = 'fulfilled'const REJECTED_STATUS = 'rejected'class MyPromise {  constructor(executor) {    // 初始化Promise的状态为pending    this.promiseStatus = PENDING_STATUS    // 初始化变量,用于保存resolve和reject传入的参数值    this.value = undefined    this.reason = undefined    // 1.定义executor需要传入的resolve函数    const resolve = (value) => {      // 只有当Promise的状态为pending,才能将状态改变fulfilled      if (this.promiseStatus === PENDING_STATUS) {        this.promiseStatus = FULFILLED_STATUS        this.value = value        console.log('调用了resolve,状态变成fulfilled啦~')      }    }    // 2.定义executor需要传入的reject函数    const reject = (reason) => {      // 只有当Promise的状态为pending,才能将状态改变为rejected      if (this.promiseStatus === PENDING_STATUS) {        this.promiseStatus = REJECTED_STATUS        this.reason = reason        console.log('调用了reject,状态变成rejected啦~')      }    }    // 3.将定义的两个函数传入executor并调用    executor(resolve, reject)  }}

简单测试一下:

// 先调用resolvenew MyPromise((resolve, reject) => {  resolve()  reject()})// 先调用rejectnew MyPromise((resolve, reject) => {  reject()  resolve()})

7.2.then方法的实现

(1)then基本实现

  • then方法接收两个参数:
    • onFulfilled回调:当Promise状态变为fulfilled需要执行的回调;
    • onRejected回调:当Promise状态变为rejected需要执行的回调
class MyPromise {  constructor(executor) {    // 初始化Promise的状态为pending    this.promiseStatus = PENDING_STATUS    // 初始化变量,用于保存resolve和reject传入的参数值    this.value = undefined    this.reason = undefined    // 1.定义executor需要传入的resolve函数    const resolve = (value) => {      // 只有当Promise的状态为pending,才能将状态改变fulfilled      if (this.promiseStatus === PENDING_STATUS) {        // 添加微任务        queueMicrotask(() => {          // 如果Promise状态不为pending,后面的代码就不再执行了          if (this.promiseStatus !== PENDING_STATUS) return          this.promiseStatus = FULFILLED_STATUS          this.value = value          // 状态变成fulfilled就去调用onFulfilled          this.onFulfilled(this.value)        })      }    }    // 2.定义executor需要传入的reject函数    const reject = (reason) => {      // 只有当Promise的状态为pending,才能将状态改变为rejected      if (this.promiseStatus === PENDING_STATUS) {        // 添加微任务        queueMicrotask(() => {          // 如果Promise状态不为pending,后面的代码就不再执行了          if (this.promiseStatus !== PENDING_STATUS) return          this.promiseStatus = REJECTED_STATUS          this.reason = reason          // 状态变成rejected就去调用onRejected          this.onRejected(this.reason)        })      }    }    // 3.将定义的两个函数传入executor并调用    executor(resolve, reject)  }  then(onFulfilled, onRejected) {    // 保存fulfilled和rejected状态的回调    this.onFulfilled = onFulfilled    this.onRejected = onRejected  }}

注意:这里将onFulfilled和onRejected的调动放在了queueMicrotask,在JavaScript中可以通过queueMicrotask使用微任务,而原Promise的then中回调的执行,也是会被放在微任务中的,为什么要放在微任务中呢?

原因:如果不使用微任务,那么在executor中执行resolve或者reject时,then方法还没被调用,onFulfilled和onRejected就都还没被赋值,所以调用时会报错,加入微任务就可以实现将onFulfilled和onRejected的调用推迟到下一次事件循环,也就是等then调用后赋值了才会执行。

简单测试一下:

const p1 = new MyPromise((resolve, reject) => {  resolve('aaaa')})const p2 = new MyPromise((resolve, reject) => {  reject('err message')})p1.then(res => {  console.log(res) // aaaa}, err => {  console.log(err)})p2.then(res => {  console.log(res)}, err => {  console.log(err) // err message})

(2)then优化一

  • 对于以上then的基本实现,还存在一些不足之处,比如:
    • then方法是可以进行多次调用的,并且每一次调用都是独立调用,互不影响,所以需要收集当Promise状态改变时,对应需要执行哪些回调,需用数组进行收集;
    • 如果then是放到定时器中调用的,那么改then的回调是不会被调用的,因为在前面我们是通过将回调添加到微任务中执行的,而定时器是宏任务,会在微任务执行完成后执行,所以定时器中then的回调就没有被调用;
    • 当then是放到定时器中执行的,那么执行的时候,微任务已经执行完成了,Promise状态肯定也确定下来了,那么只需要直接调用then中的回调即可;
class MyPromise {  constructor(executor) {    // 初始化Promise的状态为pending    this.promiseStatus = PENDING_STATUS    // 初始化变量,用于保存resolve和reject传入的参数值    this.value = undefined    this.reason = undefined    // 初始化两个数组,分别用于保存then中对应需要执行的回调    this.onFulfilledFns = []    this.onRejectedFns = []    // 1.定义executor需要传入的resolve函数    const resolve = (value) => {      // 只有当Promise的状态为pending,才能将状态改变fulfilled      if (this.promiseStatus === PENDING_STATUS) {        // 添加微任务        queueMicrotask(() => {          // 如果Promise状态不为pending,后面的代码就不再执行了          if (this.promiseStatus !== PENDING_STATUS) return          this.promiseStatus = FULFILLED_STATUS          this.value = value          // 状态变成fulfilled就去遍历调用onFulfilled          this.onFulfilledFns.forEach(fn => {            fn(this.value)          })        })      }    }    // 2.定义executor需要传入的reject函数    const reject = (reason) => {      // 只有当Promise的状态为pending,才能将状态改变为rejected      if (this.promiseStatus === PENDING_STATUS) {        // 添加微任务        queueMicrotask(() => {          // 如果Promise状态不为pending,后面的代码就不再执行了          if (this.promiseStatus !== PENDING_STATUS) return          this.promiseStatus = REJECTED_STATUS          this.reason = reason          // 状态变成rejected就去遍历调用onRejected          this.onRejectedFns.forEach(fn => {            fn(this.reason)          })        })      }    }    // 3.将定义的两个函数传入executor并调用    executor(resolve, reject)  }  then(onFulfilled, onRejected) {    // 1.如果在调用then时,Promise的状态已经确定了,就直接执行回调    if (this.promiseStatus === FULFILLED_STATUS && onFulfilled) {      onFulfilled(this.value)    }    if (this.promiseStatus === REJECTED_STATUS && onRejected) {      onRejected(this.reason)    }    // 2.如果调用then时,Promise的状态还没确定下来,就放入微任务中执行    if (this.promiseStatus === PENDING_STATUS) {      // 将then中成功的回调和失败的回调分别存入数组中      this.onFulfilledFns.push(onFulfilled)      this.onRejectedFns.push(onRejected)    }  }}

简单测试一下:

const p = new MyPromise((resolve, reject) => {  resolve('aaaa')  reject('err message')})p.then(res => {  console.log('res1:', res)}, err => {  console.log('err1:', err)})p.then(res => {  console.log('res2:', res)}, err => {  console.log('err1:', err)})setTimeout(() => {  p.then(res => {    console.log('res3:', res)  }, err => {    console.log('err1:', err)  })})

(3)then优化二

  • 通过上一步优化,then方法还存在一个缺陷,就是不能进行链式调用,在前面讲then方法时,then方法执行的返回值是一个promise对象,并且返回的promise状态是由then方法中回调函数的返回值决定的,then中必定需要返回一个新的Promise
  • 上一个then中回调的返回值可以传递到下一个then中成功的回调中,也就是返回的promise执行了resolve方法,那么什么时候可以传递到下一个then中失败的回调中呢?只需要上一个then中抛出异常即可,相当于返回的promise执行了reject方法;
  • 所以,在这里需要拿到then中回调函数返回的结果,并且需要通过try catch判断是调用resolve还是reject;
  • 注意:如果是在executor中就抛出了异常,也需要通过try catch去执行executor;
class MyPromise {  constructor(executor) {    // 初始化Promise的状态为pending    this.promiseStatus = PENDING_STATUS    // 初始化变量,用于保存resolve和reject传入的参数值    this.value = undefined    this.reason = undefined    // 初始化两个数组,分别用于保存then中对应需要执行的回调    this.onFulfilledFns = []    this.onRejectedFns = []    // 1.定义executor需要传入的resolve函数    const resolve = (value) => {      // 只有当Promise的状态为pending,才能将状态改变fulfilled      if (this.promiseStatus === PENDING_STATUS) {        // 添加微任务        queueMicrotask(() => {          // 如果Promise状态不为pending,后面的代码就不再执行了          if (this.promiseStatus !== PENDING_STATUS) return          this.promiseStatus = FULFILLED_STATUS          this.value = value          // 状态变成fulfilled就去遍历调用onFulfilled          this.onFulfilledFns.forEach(fn => {            fn(this.value)          })        })      }    }    // 2.定义executor需要传入的reject函数    const reject = (reason) => {      // 只有当Promise的状态为pending,才能将状态改变为rejected      if (this.promiseStatus === PENDING_STATUS) {        // 添加微任务        queueMicrotask(() => {          // 如果Promise状态不为pending,后面的代码就不再执行了          if (this.promiseStatus !== PENDING_STATUS) return          this.promiseStatus = REJECTED_STATUS          this.reason = reason          // 状态变成rejected就去遍历调用onRejected          this.onRejectedFns.forEach(fn => {            fn(this.reason)          })        })      }    }    // 3.将定义的两个函数传入executor并调用    // 如果executor中就抛出了异常,那么直接执行reject即可    try {      executor(resolve, reject)    } catch (err) {      reject(err)    }  }  then(onFulfilled, onRejected) {    return new MyPromise((resolve, reject) => {      // 1.如果在调用then时,Promise的状态已经确定了,就直接执行回调      if (this.promiseStatus === FULFILLED_STATUS && onFulfilled) {        // 通过try catch捕获异常,没有捕获到执行resolve,捕获到执行reject        try {          const value = onFulfilled(this.value)          resolve(value)        } catch (err) {          reject(err)        }      }      if (this.promiseStatus === REJECTED_STATUS && onRejected) {        try {          const reason = onRejected(this.reason)          resolve(reason)        } catch(err) {          reject(err)        }      }      // 2.如果调用then时,Promise的状态还没确定下来,就放入微任务中执行      if (this.promiseStatus === PENDING_STATUS) {        // 将then中成功的回调和失败的回调分别存入数组中        // 将传入的回调外包裹一层函数,目的是为了这里能够拿到then中回调执行的结果        this.onFulfilledFns.push(() => {          try {            const value = onFulfilled(this.value)            resolve(value)          } catch(err) {            reject(err)          }        })        this.onRejectedFns.push(() => {          try {            const reason = onRejected(this.reason)            resolve(reason)          } catch(err) {            reject(err)          }        })      }    })  }}

简单测试一下:

const p = new MyPromise((resolve, reject) => {  resolve('aaaa')})p.then(res => {  console.log('res1:', res)  return 'bbbb'}, err => {  console.log('err1:', err)}).then(res => {  console.log('res2:', res)  throw new Error('err message')}, err => {  console.log('err2:', err)}).then(res => {  console.log('res3:', res)}, err => {  console.log('err3:', err)})

7.3.catch方法的实现

  • catch方法的功能类似于then方法中的失败回调,所以,实现catch方法只需要调用then,给then传入失败的回调即可;
  • 只给then传入一个回调,意味着根据上面的代码,我们还需要对then的回调进行条件判断,有值才添加到对应数组中;
  • 注意:在then后链式调用catch会有一个问题,调用catch方法的promise是then执行之后返回的新promise,而catch真正需要去调用的是当前then的失败回调,而不是当前then执行后结果promise的失败回调,所以,可以将当前then的失败回调推到下一次的promise中,而抛出异常就可以实现(因为上一个then抛出异常,可以传递到下一个then的失败回调中
// catch方法实现catch(onRejected) {  return this.then(undefined, onRejected)}

then方法改进:

简单测试一下:

const p = new MyPromise((resolve, reject) => {  reject('err message')})p.then(res => {  console.log(res)}).catch(err => {  console.log(err) // err message})

7.4.finally方法的实现

  • finally方法不管是Promise状态变成fulfilled还是rejected都会被执行;
  • 这里可以巧妙的借助then方法,不管then是执行成功的回调还是失败的回调,都去执行finally中的回调即可;
  • 注意:如果在finally之前使用了catch,因为catch的实现也是去调用then,并且给then的成功回调传递的是undefined,那么执行到catch可能出现断层的现象,导致不会执行到finally,也可以通过在then中添加判断解决;
finally(onFinally) {  this.then(() => {    onFinally()  }, () => {    onFinally()  })  // 也可直接简写成:  // this.then(onFinally, onFinally)}

then方法改进:

简单测试一下:

const p = new MyPromise((resolve, reject) => {  resolve('aaaa')})p.then(res => {  console.log('res:', res) // res: aaaa}).catch(err => {  console.log('err:', err)}).finally(() => {  console.log('我是一定会执行的!') // 我是一定会执行的!})

7.5.resolve和reject方法的实现

  • resolve和reject类方法的实现就是去调用Promise中executor中的resolve和reject;
  • 注意:类方法需要加上static关键字;
static resolve(value) {  return new MyPromise((resolve, reject) => resolve(value))}static reject(reasion) {  return new MyPromise((resolve, reject) => reject(reasion))}

简单测试一下:

MyPromise.resolve('aaaa').then(res => {  console.log(res) // aaaa})MyPromise.reject('bbbb').then(res => {  console.log(res)}, err => {  console.log(err) // bbbb})

7.6.all方法的实现

  • all方法可接收一个promise数组,当所有promise状态都变为fulfilled,就返回所有promise成功的回调值(一个数组),当其中有一个promise状态变为了rejected,就返回该promise的状态;
  • all实现的关键:当所有promise状态变为fulfilled就去调用resolve,当有一个promise状态变为rejected就去调用reject
static all(promises) {  return new MyPromise((resolve, reject) => {    // 用于存放所有成功的返回值    const results = []    promises.forEach(promise => {      promise.then(res => {        results.push(res)        // 当成功返回值的长度与传入promises的长度相等,就调用resolve        if (results.length === promises.length) {          resolve(results)        }      }, err => {        // 一旦有一个promise变成了rejected状态,就调用reject        reject(err)      })    })  })}

简单测试一下:

const p1 = new MyPromise(resolve => {  setTimeout(() => {    resolve('aaaa')  }, 1000)})const p2 = new MyPromise(resolve => {  setTimeout(() => {    resolve('bbbb')  }, 2000)})const p3 = new MyPromise(resolve => {  setTimeout(() => {    resolve('cccc')  }, 3000)})MyPromise.all([p1, p2, p3]).then(res => {  console.log(res) // [ 'aaaa', 'bbbb', 'cccc' ]}).catch(err => {  console.log(err)})

7.7.allSettled方法的实现

  • allSettled方法会返回所有promise的结果数组,数组中包含每一个promise的状态和值;
  • 不管promise的状态为什么,最终都会调用resolve;
static allSettled(promises) {  return new MyPromise((resolve, reject) => {    // 用于存放所有promise的状态和返回值    const results = []    promises.forEach(promise => {      promise.then(res => {        results.push({ status: FULFILLED_STATUS, value: res })        // 当长度相等,调用resolve        if (results.length === promises.length) {          resolve(results)        }      }, err => {        results.push({ status: REJECTED_STATUS, value: err })        // 当长度相等,调用resolve        if (results.length === promises.length) {          resolve(results)        }      })    })  })}

简单测试一下:

const p1 = new MyPromise(resolve => {  setTimeout(() => {    resolve('aaaa')  }, 1000)})const p2 = new MyPromise((resolve, reject) => {  setTimeout(() => {    reject('err message')  }, 2000)})const p3 = new MyPromise(resolve => {  setTimeout(() => {    resolve('bbbb')  }, 3000)})MyPromise.allSettled([p1, p2, p3]).then(res => {  console.log(res)}).catch(err => {  console.log(err)})

7.8.race方法的实现

  • race方法是获取最先改变状态的Promise,并以该Promise的状态作为自己的状态;
static race(promises) {  return new MyPromise((resolve, reject) => {    promises.forEach(promise => {      // 得到状态最先改变的promise,调用对应的resolve和reject      promise.then(res => {        resolve(resolve)      }, err => {        reject(err)      })    })  })}

7.9.any方法的实现

  • any方法会等到有一个Promise的状态变成fulfilled,最终就是fulfilled状态;
  • 如果传入的所有Promise都为rejected状态,会返回一个AggregateError,并且可以在AggregateError中的errors属性中获取所有错误信息;
static any(promises) {  return new MyPromise((resolve, reject) => {    // 用于记录状态为rejected的值    const reasons = []    promises.forEach(promise => {      promise.then(res => {        // 当有一个promise变成fulfilled状态就调用resolve        resolve(res)      }, err => {        reasons.push(err)        // 当所有promise都是rejected就调用reject,并且传入AggregateError        if (reasons.length === promises.length) {          reject(new AggregateError(reasons))        }      })    })  })}

简单测试一下:

const p1 = new MyPromise((resolve, reject) => {  setTimeout(() => {    reject('err message1')  }, 1000)})const p2 = new MyPromise((resolve, reject) => {  setTimeout(() => {    reject('err message2')  }, 2000)})const p3 = new MyPromise((resolve, reject) => {  setTimeout(() => {    reject('err message3')  }, 3000)})MyPromise.any([p1, p2, p3]).then(res => {  console.log(res)}).catch(err => {  console.log('err:', err)  console.log(err.errors)})

7.10.Promise手写完整版整理

上面已经对Promise的各个功能进行了实现,下面就来整理一下最终的完整版,可以将一些重复的逻辑抽取出去,比如try catch。

// 定义Promise的三种状态常量const PENDING_STATUS = 'pending'const FULFILLED_STATUS = 'fulfilled'const REJECTED_STATUS = 'rejected'
// try catch逻辑抽取function tryCatchFn(execFn, value, resolve, reject) {  try {    const result = execFn(value)    resolve(result)  } catch (err) {    reject(err)  }}
class MyPromise {  constructor(executor) {    // 初始化Promise的状态为pending    this.promiseStatus = PENDING_STATUS    // 初始化变量,用于保存resolve和reject传入的参数值    this.value = undefined    this.reason = undefined    // 初始化两个数组,分别用于保存then中对应需要执行的回调    this.onFulfilledFns = []    this.onRejectedFns = []    // 1.定义executor需要传入的resolve函数    const resolve = (value) => {      // 只有当Promise的状态为pending,才能将状态改变fulfilled      if (this.promiseStatus === PENDING_STATUS) {        // 添加微任务        queueMicrotask(() => {          // 如果Promise状态不为pending,后面的代码就不再执行了          if (this.promiseStatus !== PENDING_STATUS) return          this.promiseStatus = FULFILLED_STATUS          this.value = value          // 状态变成fulfilled就去遍历调用onFulfilled          this.onFulfilledFns.forEach(fn => {            fn(this.value)          })        })      }    }    // 2.定义executor需要传入的reject函数    const reject = (reason) => {      // 只有当Promise的状态为pending,才能将状态改变为rejected      if (this.promiseStatus === PENDING_STATUS) {        // 添加微任务        queueMicrotask(() => {          // 如果Promise状态不为pending,后面的代码就不再执行了          if (this.promiseStatus !== PENDING_STATUS) return          this.promiseStatus = REJECTED_STATUS          this.reason = reason          // 状态变成rejected就去遍历调用onRejected          this.onRejectedFns.forEach(fn => {            fn(this.reason)          })        })      }    }    // 3.将定义的两个函数传入executor并调用    // 如果executor中就抛出了异常,那么直接执行reject即可    try {      executor(resolve, reject)    } catch (err) {      reject(err)    }  }  then(onFulfilled, onRejected) {    // 判断onRejected是否有值,没有值的话直接赋值一个抛出异常的方法,用于传递到下一次then中的失败回调,供catch调用    onRejected = onRejected || (err => {throw err})    // 判断onFulfilled是否有值,避免在使用catch时传入的undefined不会执行,出现断层现象    onFulfilled = onFulfilled || (value => value)    return new MyPromise((resolve, reject) => {      // 1.如果在调用then时,Promise的状态已经确定了,就直接执行回调      if (this.promiseStatus === FULFILLED_STATUS) {        // 通过try catch捕获异常,没有捕获到执行resolve,捕获到执行reject        tryCatchFn(onFulfilled, this.value, resolve, reject)      }      if (this.promiseStatus === REJECTED_STATUS) {        tryCatchFn(onRejected, this.reason, resolve, reject)      }      // 2.如果调用then时,Promise的状态还没确定下来,就放入微任务中执行      if (this.promiseStatus === PENDING_STATUS) {        // 将then中成功的回调和失败的回调分别存入数组中        // 将传入的回调外包裹一层函数,目的是为了这里能够拿到then中回调执行的结果        this.onFulfilledFns.push(() => {          tryCatchFn(onFulfilled, this.value, resolve, reject)        })        this.onRejectedFns.push(() => {          tryCatchFn(onRejected, this.reason, resolve, reject)        })      }    })  }  catch(onRejected) {    return this.then(undefined, onRejected)  }  finally(onFinally) {    this.then(onFinally, onFinally)  }  static resolve(value) {    return new MyPromise((resolve, reject) => resolve(value))  }  static reject(reasion) {    return new MyPromise((resolve, reject) => reject(reasion))  }  static all(promises) {    return new MyPromise((resolve, reject) => {      // 用于存放所有成功的返回值      const results = []      promises.forEach(promise => {        promise.then(res => {          results.push(res)          // 当成功返回值的长度与传入promises的长度相等,就调用resolve          if (results.length === promises.length) {            resolve(results)          }        }, err => {          // 一旦有一个promise变成了rejected状态,就调用reject          reject(err)        })      })    })  }  static allSettled(promises) {    return new MyPromise((resolve, reject) => {      // 用于存放所有promise的状态和返回值      const results = []      promises.forEach(promise => {        promise.then(res => {          results.push({ status: FULFILLED_STATUS, value: res })          // 当长度相等,调用resolve          if (results.length === promises.length) {            resolve(results)          }        }, err => {          results.push({ status: REJECTED_STATUS, value: err })          // 当长度相等,调用resolve          if (results.length === promises.length) {            resolve(results)          }        })      })    })  }  static race(promises) {    return new MyPromise((resolve, reject) => {      promises.forEach(promise => {        // 得到状态最先改变的promise,调用对应的resolve和reject        promise.then(resolve, reject)      })    })  }  static any(promises) {    return new MyPromise((resolve, reject) => {      // 用于记录状态为rejected的值      const reasons = []      promises.forEach(promise => {        promise.then(res => {          // 当有一个promise变成fulfilled状态就调用resolve          resolve(res)        }, err => {          reasons.push(err)          // 当所有promise都是rejected就调用reject,并且传入AggregateError          if (reasons.length === promises.length) {            reject(new AggregateError(reasons))          }        })      })    })  }}
posted @ 2022-04-03 17:33 MomentYY 阅读(16) 评论(0) 编辑 收藏 举报
回帖
    优雅殿下

    优雅殿下 (王者 段位)

    2018 积分 (2)粉丝 (47)源码

    小小码农,大大世界

     

    温馨提示

    亦奇源码

    最新会员