阅读 93

Promise原理解析

前言

  • 最近通过学习javascrip的异步处理了解到了Promise对象,并学习了对Promise原理的分析,以此记录!

Promise的原理解析

  • 通过封装库的思想手动构造一个Promise我学习到从以下几个方面去考虑

    1. Promise 中三种状态的实现

    2. Promise 中then的实现方法

    3. Promsie 中

一、Promise 的三种状态

  • Promise中默认 pending 状态,调取 resolve方法 成功返回 fulfilled 状态,调取 reject方法 成功返回 rejected 状态

// Promise类  let p = new Promise((resolve, reject) => {      // resolve('success')      // reject('err')   })  console.log(p) // 返回Promise对象 其中[[PromiseState]]: "pending" [[PromiseResult]]: undefined 复制代码

image.png
根据返回对象我们可以自己构造一个方法实现简单的展现Promise的状态

class MPromise {         constructor(handle) {           // 默认状态           this['[[PromiseState]]'] = 'pending'           this['[[PromiseResult]]'] = undefined           handle(this.#resolve.bind(this), this.#reject.bind(this))         }         // 成功时调取函数         #resolve(val) {           this['[[PromiseState]]'] = 'fulfilled'           this['[[PromiseResult]]'] = val         }         // 失败时调取函数         #reject(err) {           this['[[PromiseState]]'] = 'rejected'           this['[[PromiseResult]]'] = err         }       }       let p = new MPromise((resolve, reject) => {         // resolve('success')         reject('err')       })       console.log(p) //  复制代码

image.png
这样就实现了一个简易的呈现状态的Promise

二、then的问题

  • 多个 then 的实现

  • then 的返回值

  • 微任务宏任务

  • 链式操作

实现then方法

Promise中这样调取

let p = new Promise((resolve, reject) => {         resolve('success')         reject('err')       })       p.then((res) => {         console.log(res) // success       },err=>{         console.log(err) // err       }) 复制代码

手动构造一个 then 的方法 注意:then 方法不能立刻执行,当调取resolve或者reject之后才能执行 then 否则直接执行 then 延迟执行resolve或reject时会抛出undefined,代码如下:

class MPromise {         constructor(handle) {           // 默认状态           this['[[PromiseState]]'] = 'pending'           this['[[PromiseResult]]'] = undefined           handle(this.#resolve.bind(this), this.#reject.bind(this))         }         // 成功时调取函数         #resolve(val) {           this['[[PromiseState]]'] = 'fulfilled'           this['[[PromiseResult]]'] = val         }         // 失败时调取函数         #reject(err) {           this['[[PromiseState]]'] = 'rejected'           this['[[PromiseResult]]'] = err         }         then(onResolved, onRejected) {           if ((this['[[PromiseState]]'] = 'fulfilled')) {             onResolved && onResolved(this['[[PromiseResult]]'])           } else {             onRejected && onRejected(this['[[PromiseResult]]'])           }         }       }       let p = new MPromise((resolve, reject) => {         setTimeout(() => {           resolve('success')           // reject('err')         })       })       p.then((res) => {         console.log(res)       } 复制代码

image.png 那么解决这一问题的方法就是在调取resolve或者reject后执行,代码如下:

class MPromise {         constructor(handle) {           // 默认状态           this['[[PromiseState]]'] = 'pending'           this['[[PromiseResult]]'] = undefined           this.resolveFn = undefined           this.rejectFn = undefined           handle(this.#resolve.bind(this), this.#reject.bind(this))         }         // 成功时调取函数         #resolve(val) {           this['[[PromiseState]]'] = 'fulfilled'           this['[[PromiseResult]]'] = val           this.resolveFn(val)         }         // 失败时调取函数         #reject(err) {           this['[[PromiseState]]'] = 'rejected'           this['[[PromiseResult]]'] = err           this.resolveFn(val)         }         then(onResolved, onRejected) {           // 将状态保存起来           this.resolveFn = onResolved           this.rejectFn = onRejected         }       } 复制代码

这样我们延迟调用resolve或reject就不会出现undefined现象

多个then的问题

上面的方法虽然解决延时调用的问题,但如果我们调用多个 then 方法会出现覆盖问题,总是获取到最后一次调取的值,所以多个 then 的解决方法就是通过数组存储,依次执行

   class MPromise {         constructor(handle) {           // 默认状态           this['[[PromiseState]]'] = 'pending'           this['[[PromiseResult]]'] = undefined           this.resolveQueue = []           this.rejectQueue = []           handle(this.#resolve.bind(this), this.#reject.bind(this))         }         // 成功时调取函数         #resolve(val) {           this['[[PromiseState]]'] = 'fulfilled'           this['[[PromiseResult]]'] = val           const run = () => {             let cb             while ((cb = this.resolveQueue.shift())) {               cb && cb(val)             }           }           run()         }         // 失败时调取函数         #reject(err) {           this['[[PromiseState]]'] = 'rejected'           this['[[PromiseResult]]'] = err           const run = () => {             let cb             while ((cb = this.resolveQueue.shift())) {               cb && cb(err)             }           }           run()         }         then(onResolved, onRejected) {           // 将状态保存起来           this.resolveQueue.push(onResolved)           this.rejectQueue.push(onRejected)         }       }       let p = new MPromise((resolve, reject) => {         setTimeout(() => {           resolve('success')           // reject('err')         })       })       p.then((res) => {         console.log(res)       })       p.then((res) => {         console.log('22')       }) 复制代码

image.png
但是这样还有一个执行顺序问题,如果同步执行的话,最后执行 then 方法,导致同步执行没有结果,解决这一问题的方法就是先执行 then 方法,所以让resolve和reject变成异步方法,代码如下:

// 将上面代码中run方法修改为异步方法即可 setTimeout(run) 复制代码

这样不论是同步还是异步,调用 then 都会获取结果

微任务与宏任务

上面我们解决了同步和异步时都会调用 then 方法的问题,然而还有一个问题需要解决,先看问题:

// 输出顺序问题 // Promise setTimeout(() => {         console.log('11')       })       console.log('22')       let p = new Promise((resolve, reject) => {         resolve('33')       })       p.then((res) => {         console.log(res)       })       p.then((res) => {         console.log('44')       })       console.log('55') // 输出顺序为 22-55-33-44-11 复制代码

// 自定义方法 // MPromise为上述方法  setTimeout(() => {         console.log('11')       })       console.log('22')       let p = new MPromise((resolve, reject) => {         resolve('33')       })       p.then((res) => {         console.log(res)       })       p.then((res) => {         console.log('44')       })       console.log('55') // 输出顺序为 22-55-11-33-44 复制代码

  • 微任务:一个需要异步执行的函数,执行时机是在主函数执行结束自后、当前宏任务结束之前

  • 宏任务:宏任务的时间粒度比较大,执行的时间间隔是不能精确控制的,对一些高实时性的需求就不太符合

then 方法是一个微任务

  • 执行时 首先是同步代码执行,所以先输出 22 55 之后执行异步代码,因为一个宏任务执行后要先执行它所包含的微任务,然后在去执行下一个宏任务,setTimeout属于下一个宏任务,所以先输出 33 44

那如何修改自己构造的方法呢?
MutationObserver 方法
developer.mozilla.org/zh-CN/docs/…
利用这个方法我们可以通过监听属性变化,进行函数调用,变成异步微任务,代码如下:

class MPromise {         constructor(handle) {           // 默认状态           this['[[PromiseState]]'] = 'pending'           this['[[PromiseResult]]'] = undefined           this.resolveQueue = []           this.rejectQueue = []           handle(this.#resolve.bind(this), this.#reject.bind(this))         }         // 成功时调取函数         #resolve(val) {           this['[[PromiseState]]'] = 'fulfilled'           this['[[PromiseResult]]'] = val           const run = () => {             let cb             while ((cb = this.resolveQueue.shift())) {               cb && cb(val)             }           }           const observe = new MutationObserver(run)           observe.observe(document.body, { attributes: true })           document.body.setAttribute('name', 'a')         }         // 失败时调取函数         #reject(err) {           this['[[PromiseState]]'] = 'rejected'           this['[[PromiseResult]]'] = err           const run = () => {             let cb             while ((cb = this.resolveQueue.shift())) {               cb && cb(err)             }           }           const observe = new MutationObserver(run)           observe.observe(document.body, { attributes: true })           document.body.setAttribute('name', 'a')         }         then(onResolved, onRejected) {           // 将状态保存起来           this.resolveQueue.push(onResolved)           this.rejectQueue.push(onRejected)         }       }       setTimeout(() => {         console.log('11')       })       console.log('22')       let p = new MPromise((resolve, reject) => {         resolve('33')       })       p.then((res) => {         console.log(res)       })       p.then((res) => {         console.log('44')       })       console.log('55')       // 通过MutationObserver模拟一个微任务,所以输出顺序就是 22-55-33-44-11 复制代码

链式操作

先来看一下 Promise 中 then 的链式操作

 let p = new Promise((resolve, reject) => {         resolve('success')       })       p.then((res) => {         console.log(res)         return new Promise((resolve, reject) => {           resolve('success')         })       }).then((res) => {         console.log(res)       })    // 打印两个success 复制代码

也就是说在 Promise 的 then 中可以通过 return 返回并通过下一个 then 调取,所以我的思路就是先让自己构造的 then 方法可以返回一个新包装的 Promise 对象 代码如下:

class MPromise {         constructor(handle) {           // 默认状态           this['[[PromiseState]]'] = 'pending'           this['[[PromiseResult]]'] = undefined           this.resolveQueue = []           this.rejectQueue = []           handle(this.#resolve.bind(this), this.#reject.bind(this))         }         // 成功时调取函数         #resolve(val) {           this['[[PromiseState]]'] = 'fulfilled'           this['[[PromiseResult]]'] = val           const run = () => {             let cb             while ((cb = this.resolveQueue.shift())) {               cb && cb(val)             }           }           const observe = new MutationObserver(run)           observe.observe(document.body, { attributes: true })           document.body.setAttribute('name', 'a')         }         // 失败时调取函数         #reject(err) {           this['[[PromiseState]]'] = 'rejected'           this['[[PromiseResult]]'] = err           const run = () => {             let cb             while ((cb = this.resolveQueue.shift())) {               cb && cb(err)             }           }           const observe = new MutationObserver(run)           observe.observe(document.body, { attributes: true })           document.body.setAttribute('name', 'a')         }         then(onResolved, onRejected) {           return new MPromise((resolve, reject) => {             // 拿到结果 且不可立即执行             let resolveFn = function (val) {               let result = onResolved && onResolved(val)               // 返还MPromise对象               if (result instanceof MPromise) {                 result.then(resolve)               } else {                 resolve(result)               }             }             this.resolveQueue.push(resolveFn)             let rejectFn = function (err) {               onRejected && onRejected(err)               reject(err)             }             this.rejectQueue.push(rejectFn)           })           // 将状态保存起来         }       }       let p = new MPromise((resolve, reject) => {         resolve('33')       })       p.then((res) => {         console.log(res)         return new MPromise((resolve) => {           resolve('11')         })       }).then((res) => {         console.log(res)       })       // 结果打印 33,11 复制代码

三、周边方法

原型方法

catch
catch主要是通过 then 方法实现的 通过给 then 中的onReject传入值即可

 catch(fn) {           return this.then(undefined, fn)         }  let p = new MPromise((resolve, reject) => {      reject('err')  })  p.then((res) => {      console.log(res)  }).catch((err) => {      console.log(err) // err  }) 复制代码

finally

finally(cb) {     this.then(cb, cb) } 复制代码

finally方法放在then后面执行就好

静态方法

resolve/reject
也是通过返回一个 MPromise 对象来构造的方法

static resolve(val) {           return new MPromise((resolve) => {             resolve(val)           })         } static reject(err) {          return new MPromise((resolve, reject) => {             reject(err)          })         } let p = MPromise.reject('err') console.log(p) // 返回一个 MPromise 对象          复制代码

race
race 的参数是一个包含Promise对象的数组

let p1 = new Promise((resolve, reject) => {         setTimeout(() => {           resolve('success')         }, 2000)       })       let p2 = new Promise((resolve, reject) => {         setTimeout(() => {           reject('err')         }, 1000)       })       Promise.race([p1, p2]).then(         (res) => {           console.log(res)         },         (err) => {           console.log(err)         }       )       // 结果打印 err 复制代码

自己实现的思路就是定义一个静态函数返回Promise对象

static race(lists) {           return new MPromise((resolve, reject) => {             lists.forEach((item) => {               item.then(                 (res) => {                   resolve(res)                 },                 (err) => {                   reject(err)                 }               )             })           })         } 复制代码

all
只返回resolve中的值

 static all(lists) {           let resArr = []           let num = 0           return new MPromise((resolve) => {             lists.forEach((item) => {               item.then((res) => {                 num++                 resArr.push(res)                 if (resArr.length === lists.length) {                   resolve(resArr)                 }               })             })           })         } 复制代码

allSettled
allSettled 会把 Promise 成功或者失败的值都获取到

 let p1 = new Promise((resolve, reject) => {         setTimeout(() => {           resolve('success')         }, 1000)       })       let p2 = new Promise((resolve, reject) => {         setTimeout(() => {           reject('err')         }, 2000)       })       Promise.allSettled([p1, p2]).then((res) => {         console.log(res)       }) 复制代码

image.png 手动实现 allSettled 方法

  static allSettled(lists) {           let resArr = new Array(lists.length)           let num = 0           return new MPromise((resolve,reject) => {             lists.forEach((item, key) => {               let obj = {}               item.then(                 (res) => {                   obj['statue'] = 'fulfilled'                   obj['val'] = res                   resArr[key] = obj                   num++                   if (num >= lists.length) {                     resolve(resArr)                   }                 },                 (err) => {                   obj['statue'] = 'rejected'                   obj['val'] = err                   resArr[key] = obj                   num++                   if (num >= lists.length) {                     reject(resArr)                   }                 }               )             })           })         } 复制代码

总结

个人觉得对 Promise 的理解可能还是没有很深入的理解,毕竟刚刚入门 希望看到这篇文章的人有什么想法能够和笔者交流,有问题的地方及时指出,共同进步!


作者:爱泡澡的小萝卜
链接:https://juejin.cn/post/7022178384520151070


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐