手写Promise笔记(上)

手写Promise感觉还是挺常见的,这里记录下自己构建整个Promise的过程,主要参考的是后盾人Promise教程,记录下看完之后的笔记和个人的思考。

Promise的基本知识

  1. Promise包含pending等待,fulfilled成功,rejected拒绝三种状态,当时pending状态的时候可以转换为成功/拒绝状态,并且转换后不能再转换。
  2. 一个promise必须有一个then方法,then方法接受两个参数promise.then(onFulfilled,onRejected),这里onFulfilled方法是成功状态的处理函数,onRejected方法是失败状态的处理函数。
  3. 为了实现链式调用,then方法也要返回一个promise

手写Promise

不多说直接放全部代码,具体笔记和理解在备注中,部分的笔记图片可以看代码后的图片

class myPromise {
   // 声明3个状态,分别为pending等待/resolve解决/reject拒绝
   static PENDING = 'pending'
   static FULFILLED  = 'fulfilled'
   static REJECT = 'rejected'
   constructor (executor) {
      // executor为执行者
      // 一开始是等待状态
      this.status = myPromise.PENDING
      this.value = null
      // 用于保存需要执行的函数
      this.callbacks = []
      // 这里使用try..catch的原因是如果在promise中出现了错误信息的情况,就直接丢给reject来处理
      try {
         // class内this遵循严格模式,此处的this是指向的window
         // 使用bind认为干预this指向
         executor(this.resolve.bind(this),this.reject.bind(this));
      } catch (error) {
         // 把错误信息给reject来处理
         this.reject(error)
      }
   }
   // 类方法
   resolve(value) {
      // 这里需要增加一个判断,如果当前Promise的状态为pending的时候,才能进行状态更改和处理
      if(this.status === myPromise.PENDING) {
        // 执行类方法的时候就要改变Promise的状态和值
        this.status = myPromise.FULFILLED
        this.value = value
        setTimeout(() => {
           // 这里还是要处理为异步任务,如果promise内出现异步处理的函数内还有同步任务
           // 那么需要先解决同步任务,再进行Promise的状态改变和处理
           // 可以结合后文的图片来理解[图:Pending状态下的异步处理]
           this.callbacks.map(callback => {
              // 这里应该这样理解,当状态异步改变的时候,先把then里面的方法保存起来,然后等状态改变了
              // 就从之前保存的函数中,拿到then里面的对应方法执行
              callback.onFulfilled(value)
         })
        })
      }
   }
   reject(reason) {
      if(this.status === myPromise.PENDING) {
         this.status = myPromise.REJECT
         this.value = reason
         setTimeout(() => {
            this.callbacks.map(callback => {
               // 这里用map应该是存入的时候使用了数组push,这里map会对每个数组数据进行处理,这里数组只有1个数据
               // 所以是执行1次,存入的又是一个对象所以可以用callback.onRejected获取对应函数
               callback.onRejected(reason)
            })
         })
       }
   }
   // then方法的实现
   then(onFulfilled,onRejected) {
      // 两个函数不是必须传入的,例如会出现then(null,onRejected)的情况
      if(typeof onFulfilled !== 'function') {
         // 自己封装一个空函数上去
         // 同时返回value的值,由此来实现穿透
         onFulfilled = () => {return this.value}
      }
      if(typeof onRejected !== 'function') {
         // 自己封装一个空函数上去
         onRejected = () => {return this.value}
      }
      // 这里是返回一个全新的promise,是为了让then支持链式调用
      // 将返回的Promise进行保存用于之后返回判断
      let promise =  new myPromise((resolve,reject) => {
         if(this.status === myPromise.PENDING) {
            // 这里是等待状态,在promise内部的函数是异步执行的,例如过多少秒之后才解决,
            // 那么我们手写的Promise就应该在pending状态下,保存需要执行的函数.
            // 异步改变状态的时候,会先来到这里保存函数
            this.callbacks.push({
               // 这里将保存的函数进行了一次包装,如果异步then中出现了错误,也会全部交给onRejected来处理
               onFulfilled: value => {
                  this.parse(promise,onFulfilled(value),resolve,reject)
                  // try {
                  //    // 这里的更改主要是针对Promise的内部函数的异步处理
                  //    let result = onFulfilled(value)
                  //    // 通过instanceof来判断result是否是通过myPromise实现的,从而确定是否是返回的Promise
                  //    if (result instanceof myPromise) {
                  //       // 如果是Promise的话,目的还是要改变Promise的状态,并且返回值
                  //       // 此时 result 是一个Promise
                  //       // 这里相当于重新执行了一次返回的Promise,递归
                  //       result.then(resolve,reject)
                  //    } else {
                  //       // 普通值直接返回
                  //       resolve(result)
                  //    }
                  // } catch (error) {
                  //    // then的异步处理中出现error就交给onRejected处理
                  //    // 同时这里的处理函数从onRejected改为reject,相当于把错误代码交给了
                  //    // 最后一个then来处理
                  //    reject(error)
                  // }
               },
               onRejected: reason => {
                  this.parse(promise,onRejected(reason),resolve,reject)
               },
            })
         }
         // 这里判断状态,必须是解决状态的情况才会执行onFulfilled函数,否则会出现状态没改变也执行的情况
         if(this.status === myPromise.FULFILLED) {
            // 使用setTimeout将then中的处理变成异步方法,这样才能符合正常promise的运行顺序
            // 这时这些代码不会立即执行,而是进入下一个loop中
            // 这里有点小问题,是setTimeout是宏任务,和自带promise相比,这个会比自带的then晚执行
            setTimeout(() => {
               // 因为链式调用把之前的值保存,然后传递给下一个then
               // 函数要是运行了就是把值传递,没有就是传递函数
               this.parse(promise,onFulfilled(this.value),resolve,reject)
            }, 0)
         }
         if(this.status === myPromise.REJECT) {
            setTimeout(() => {
               this.parse(promise,onRejected(this.value),resolve,reject)
            }, 0)
         }
      })
      return promise
   }
   // 代码复用
   parse(promise,result,resolve,reject) {
      if(promise === result) {
         // 这里的判断用于限制Promise.then不能返回自身
         throw new TypeError('aaa')
      }
      // 使用try..catch也是为了捕获then中出现的错误,只有写了catch才会有错误信息输出
      try {
           // 这里的更改主要是针对Promise的内部函数的异步处理
           // 通过instanceof来判断result是否是通过myPromise实现的,从而确定是否是返回的Promise
           if (result instanceof myPromise) {
              // 如果是Promise的话,目的还是要改变Promise的状态,并且返回值
              // 此时 result 是一个Promise
              // 这里相当于重新执行了一次返回的Promise,递归
              result.then(resolve,reject)
           } else {
              // 普通值直接返回
              resolve(result)
           }
        } catch (error) {
           // then的异步处理中出现error就交给onRejected处理
           // 同时这里的处理函数从onRejected改为reject,相当于把错误代码交给了
           // 最后一个then来处理
           reject(error)
        }
   }
   // resolve静态方法
   static resolve(value) {
      // 主要还是返回一个Promise
      return new myPromise((resolve,reject) => {
         //也是需要判断传入的是不是Promise
         if (value instanceof myPromise) {
            // 如果传入的是Promise,那么状态就和传入的Promise一样
            value.then(resolve,reject)
         } else {
            // 这种是原生状态,普通值直接返回
            resolve(value)
         }
      })
   }
   // reject静态方法实现
   static reject(value) {
      // 主要还是返回一个Promise
      return new myPromise((resolve,reject) => {
            reject(value)
         })
   }
   // all静态方法,都成功才成功,返回的还是Promise
   static all(promises) {
      return new myPromise((resolve,reject) => {
         const values = []
         promises.forEach(promise => {
            promise.then(
               value => {
                  // 每次成功就保存一次值
                  values.push(value)
                  if(values.length === promises.length) {
                     // 如果存入结果的数组长度等于传入promise的数组长度,那么就相当于所有的Promise都是成功的
                     resolve(values)
                  }
               },
               reason =>{
                  // 任何一个传入的Promise的状态为reject的话就改变返回的all的promise的状态
                  // 上面的resolve就不会继续走了
                  reject(reason)
               })
         })
      } )
      
   }
   // race静态方法,谁快用谁,不管成功还是失败,返回的也是Promise
   static race(promises) {
      return new myPromise((resolve,reject) => {
         promises.map(promise => {
            promise.then(
               value => {
                  // 状态只改变一次,所以直接使用就可以
                  resolve(value)
               },
               reason =>{
                  reject(reason)
               }
            )
         })
      })
   }
}

这里需要输出的结果是taec-aaa-解决,但是如果把resolve内处理成异步的话输出的结果就是taec-解决-aaa,setTimeout内是全部当做同步任务处理。

Pending状态下的异步处理

then支持链式操作的前提是之前的then返回一个promise即可。而且then返回的Promise的状态都是成功的。
then链式调用

如果处理过程中发生,错误那么会要把错误信息传递给最后一个then的reject来处理,所以在返回的时候,所有的错误处理函数都用的是reject()
reject的修改

then链式调用错误处理

可正常接收到抛出的错误

then穿透.then 或者 .catch 的参数期望是函数,传入非函数则会发生值穿透。
例如:

Promise.resolve(1)
  .then(function(){return 2})
  .then(Promise.resolve(3))
  .then(console.log) //    2

then穿透实现

实现穿透验证resolve

实现穿透验证reject

then返回Promise需要将返回的Promise重新执行,通过instanceof来判断返回的值是否是通过手写的myPromise来实现的,如果是就执行一遍返回的Promise的then函数,那么就会获取到值,如果不是就直接执行并且返回值,核心是递归调用了。
返回Promise验证

resolve/reject静态方法实现,使用static关键字,也是分传入的是Promise和普通值两种情况
静态方法实现

promise.all方法Promise.all([p1,p2]).then(value => {console.log(value)}),需要p1、p2两个都为成功才能获取到数据,否则就不行。
all方法测试

promise.race方法,几个Promise谁先完成就先用谁,状态只改变一次。
race方法测试

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容