10.手写封装 promise, 模拟 promise 源码1

加载图片,用promise

            function loadURL (url) {
              let img = new Image();
                return new Promise((resolve,reject) => {
                  
                  img.onload = function () {
                    resolve(img);
                    /11111
                  }
                  img.onerror = function () {
                    reject();
                    throw `${url} is nor correct`
                  }
                  img.src = url
                })
            }

resolve(img) 有两个作用, 一个是 改变状态,声明状态
另一个是将返回值传递出去.


promise细致用法


image.png
            let p = new Promise((res,rej) => {
              res();
              rej();
          
            })
image.png

可以看出

  • promise 状态是不可逆的.
            let p = new Promise((res,rej) => {
              res('suc');
              rej('err');
            })
                
            let p1 = p.then((data) => {console.log(data,'p');},(data) => {console.log(data,'p');});
                     p1.then((data) => {console.log(data,'p1');},(data) => {console.log(data,'p1');})
image.png

原来如此, 我还在想呢, 如果每次都要 new Promise, 那then 的链式调用也没太大意思
想要链式调用,要解决两个东西, 一个是状态的改变, 一个是数据的传递.
then里执行过后, 默认下个then状态都会变成 resolve
猜测, p 的两个回调 ,如果执行了第一个, 就会触发 p1 的第一个回调

  • then方法会返回promise对象, 对应回调函数中的return 作为Promise对象中的data
            let p = new Promise((res,rej) => {
              res('suc');
              rej('err');
            })
                
            let p1 = p.then((data) => {console.log(data,'p');return "sucsuc"},(data) => {console.log(data,'p');return "errerr"});
                     p1.then((data) => {console.log(data,'p1');},(data) => {console.log(data,'p1');})
imag.png

Promise.resolve()
返回一个带有明确data且状态为 rosolved 的 Promise对象.

            let p = Promise.resolve(123);
            p.then((data) => {console.log(data);})/123

Promise.reject()
返回一个带有明确data且状态为 rejected的 Promise对象.

            let p = Promise.reject(123);
            p.then(()=>{},(data) => {console.log(data);})/123

Promise.all([p1,p2,p3])

            let p1 = Promise.resolve(111);
            let p2 = new Promise((res,rej) => {
              setTimeout(function () {
                res(222)
              },1000)
            })
            let p3 = new Promise((res,rej) => {
              setTimeout(function () {
                res(333)
              },2000)
            })
            
            let P = Promise.all([p1,p2,p3]);
            P.then((data) => {console.log(data);},(data) => {console.log(data);})
image.png

只有当三个全为 resolved 时, 才会触发
执行时间取决于最后一个 执行resolve的promise对象

            let p1 = Promise.resolve(111);
            let p2 = new Promise((res,rej) => {
              setTimeout(function () {
                rej(222)
              },1000)
            })
            let p3 = new Promise((res,rej) => {
              setTimeout(function () {
                rej(333)
              },2000)
            })
            
            let P = Promise.all([p1,p2,p3]);
            P.then((data) => {console.log(data);},(data) => {console.log(data);})
            
image.png

可知, 只要有一个 触发 reject 就会触发,并且只返回第一个reject的p的数据
执行时间取决于第一个触发 reject的promise对象.

Promise.all() 的用处
比如, 我要请求三次数据, 我可以每次获取数据都渲染一次页面,(3次)
也可以三个数据都获得之后 再渲染一次页面(1次)
但只要有一个数据是无法获取, 就要渲染一个 error提示页面
这个时候用 Promise.all() 就很适合.
类似的, 对于管理多个并行异步请求, 似乎是比较适合的.
并且这几个请求缺一不可.

这里的并行异步请求 指的是 各个请求之间没有顺序上的依赖.
如果存在顺序上的依赖, 明显用then 比较合适.


Promise.race([p1,p2,p3])

            let p1 = new Promise((res,rej) => {
              setTimeout(function () {
                res(111)
              },3000)
            })
            let p2 = new Promise((res,rej) => {
              setTimeout(function () {
                rej(222)
              },1000)
            })
            let p3 = new Promise((res,rej) => {
              setTimeout(function () {
                rej(333)
              },2000)
            })
            
            let P = Promise.race([p1,p2,p3]);
            P.then((data) => {console.log(data);},(data) => {console.log(data);})
            

可知
Promise.race() 求的是三个请求中最快的那个,
不关心 请求的结果如何.(这样就没什么太大意思啊)
通常用于 网络测试?


Promise.prototype.catch()

            Promise.resolve(1).catch((data) => {console.log(data)});/ 没有触发
            Promise.reject(1).catch((data) => {console.log(data)}); / 返回1

catch()是 用来代替 p.then(null,(data)=>{}) 的?

            new Promise((res,rej) => {
              rej('err')
            }).then((data) => {console.log(data)}).catch((data) => {console.log(data)})/ err
            
            new Promise((res,rej) => {
              rej('err')
            }).then((data) => {console.log(data)},data => {
              console.log(123);
              return data;
            }).catch((data) => {console.log(data)})
image.png

表明执行了 then(null,()=>{})之后, 就不会再执行catch 了?


为什么不看源码?
因为看不懂
代码的28法则
一个完整的代码总是包含很多辅助性的代码
这些辅助性的代码的作用是 增强代码的稳定性,健壮性,安全性
这些辅助性的代码 会让代码变得很难阅读
核心代码占据 20% 辅助性代码占据80%

模拟封装promise
step 1 模拟参数校验, 以及基本架构

        class myPromise {
            constructor (fn) {
              if (typeof fn !== "function") {
                throw TypeError(`myPromise resolver ${fn} is not a function`)
              }
              this.state = "pending";
              this.data = undefined;
              
              let resolve = (data) => {
                this.state = "resolved"
                this.data = data;
              }
              let reject = (data) => {
                this.state = "rejected"
                this.data = data;
              }
              
              fn(resolve,reject);
            }
            then () {
              
            }
            static resolve () {
              
            }
            static reject () {
              
            }
        }
        
        new myPromise(123);

step2 模拟状态不可逆

              let resolve = (data) => {
                if (this.state == "pending") {
                  this.state = "resolved"
                  this.data = data;
                }
              }
              let reject = (data) => {
                if (this.state == "pending") {
                  this.state = "rejected"
                  this.data = data;
                }
              }

step3 模拟 Promise.resolve,Promise.reject

            static resolve (data) {
              return new myPromise((suc) => {
                suc(data);
              })
            }
            static reject (data) {
             return new myPromise((suc,err) => {
              err(data);
            })
            }

step4 模拟 then

            then (resolveFn, rejectFn) {
              if (this.state == "resolved") {
                resolveFn(this.data);
              }
              if (this.state == "rejected") {
                rejectFn(this.data);
              }
            }

step5 判断回调的返回值是否为 promise 对象

            then (resolveFn, rejectFn) {
              if (this.state == "resolved") {
                let rus = resolveFn(this.data);
                if (rus instanceof myPromise) {
                    console.log('is a promise');
                }else{
                  console.log(" not a promise");
                }
              }
              if (this.state == "rejected") {
                rejectFn(this.data);
              }
            }

step6 根据判断 返回不同的 promise 对象,
这里是稍微难的, 感觉很巧妙.不容易想到.

            then (resolveFn, rejectFn) {
              if (this.state == "resolved") {
                let rus = resolveFn(this.data);
                if (rus instanceof myPromise) {
                          // 返回新 promise 对象 状态 数据都由 new时定义
                  return rus
                }else{
                          // 也是返回新promise 对象, 但状态确定为 resolved, 数据则为 rus, 这个数据储存在这个新对象的 this.data中, 便于这个对象的then调用
                  return myPromise.resolve(rus);
                }
              }
              if (this.state == "rejected") {
                let rus = rejectFn(this.data);
                if (rus instanceof myPromise) {
                return rus
              }else{
                  return myPromise.resolve(rus);
              }
              }
            } 

到此时,先贴一下整体代码

        class myPromise {
            constructor (fn) {
              if (typeof fn !== "function") {
                throw TypeError(`myPromise resolver ${fn} is not a function`)
              }
              this.state = "pending";
              this.data = undefined;
              
              let resolve = (data) => {
                if (this.state == "pending") {
                  this.state = "resolved"
                  this.data = data;
                }
              }
              let reject = (data) => {
                if (this.state == "pending") {
                  this.state = "rejected"
                  this.data = data;
                }
              }
              
              fn(resolve,reject);
            }
            then (resolveFn, rejectFn) {
              if (this.state == "resolved") {
                let rus = resolveFn(this.data);
                if (rus instanceof myPromise) {
                  return rus
                }else{
                  return myPromise.resolve(rus);
                }
              }
              if (this.state == "rejected") {
                let rus = rejectFn(this.data);
                if (rus instanceof myPromise) {
                return rus
              }else{
                  return myPromise.resolve(rus);
              }
              }
            } 
            static resolve (data) {
              return new myPromise((suc) => {
                suc(data);
              })
            }
            static reject (data) {
             return new myPromise((suc,err) => {
              err(data);
            })
            }
        }

不过无法解决 异步的问题.

作业1, 用 原生写一遍以上代码

      function myPromise(fn) {
        if(typeof fn !== "function") {
          throw TypeError(`${fn} is not a function`)
        }

        this.state = "pending";
        this.data = undefined;
        
        
        // 这个地方不用箭头函数, 就需要把 this用变量存一下
        let _this = this;
        let resolve = function(data) {
          if(_this.state == "pending") {
            _this.state = "resolved";
            _this.data = data;
          }
        }
        let reject = function(data) {
          if(_this.state == "pending") {
            _this.state = "rejected";
            _this.data = data;
          }
        }

        fn(resolve, reject);

      }

      myPromise.resolve = function(data) {
        return new myPromise((res) => {
          res(data);
        })
      }
      myPromise.reject = function(data) {
        return new myPromise((res,rej) => {
          rej(data);
        })
      }
      myPromise.prototype.then = function(resolveFn, rejectFn) {
        if (this.state == "resolved") {
            let rus = resolveFn(this.data);
            if (rus instanceof myPromise) {
                return rus
            }else{
              return myPromise.resolve(rus);
            }
        }
        if (this.state == "rejected") {
            let rus = rejectFn(this.data);
            if (rus instanceof myPromise) {
                return rus
            }else {
              return myPromise.reject(rus);
            }
        }
      }

作业2 思考一下如何解决异步问题,
也就是 state == 'pending'时该怎么办?

什么时候resolve 执行, 改变了状态, 什么时候就执行 then 里面的回调函数.
也就是说, then里的函数,应该是先进行注册,进行保存,
当 状态发生变化时, 相应保存的函数再执行.
并且执行完之后,再返回 promise对象.
then是链式调用,每个then不能马上返回promise对象,
那样会出问题.

所以关键是要存这个函数. 然后再考虑怎么返回promise对象,
因为还要把注册的不同的函数, 分配给不同的promise 对象?

step1

      class myPromise {
        constructor(fn) {
          if(typeof fn !== "function") {
            throw TypeError(`myPromise resolver ${fn} is not a function`)
          }
          this.state = "pending";
          this.data = undefined;
          // 用来储存 resolveFn的数组
          this.resList = [];
          // 用来储存 rejectFn的数组
          this.rejList = [];
          
          let resolve = (data) => {
            if(this.state == "pending") {
              this.state = "resolved"
              this.data = data;
              if (this.resList.length > 0) {
                this.resList.shift()();
              }
            }
          }
          let reject = (data) => {
            if(this.state == "pending") {
              this.state = "rejected"
              this.data = data;
              if (this.rejList.length > 0) {
                this.rejList.shift()();
              }
            }
          }
          
          fn(resolve, reject);
        }
        
        then(resolveFn, rejectFn) {
          
          this.resList.push( () => {
            let rus = resolveFn(this.data);
            if(rus instanceof myPromise) {
              return rus
            } else {
              return myPromise.resolve(rus);
            }
          })
            
            this.rejList.push(() => {
                let rus = rejectFn(this.data);
            if(rus instanceof myPromise) {
              return rus
            } else {
              return myPromise.reject(rus);
            }
            })
        }
        static resolve(data) {
          return new myPromise((suc) => {
            suc(data);
          })
        }
        static reject(data) {
          return new myPromise((suc, err) => {
            err(data);
          })
        }
      }

      let p = new myPromise((suc, err) => {
        setTimeout(function () {
            err('suc');
        },2000);
      });

      p.then(null, (data) => console.log(data))

这样处理,能够完成第一步的异步,
但一旦链式调用, 则会报错

      p.then(null, (data) => console.log(data)).then(null,data => console.log(data))
image.png

这很正常, 因为 我们的then在执行第一次后没有返回任何东西.
我们应该返回一个临时promise对象?,你不能返回跟原来的一样的,
这个promise对象只需要做一件事情, 就是把 then里的回调都注册进来.
问题是, 我们是不是应该把所有的回调都储存在同一个数组?
应该, 因为我们还要分出去, 所以这个数组不能放在实例上,
而应该放在原型上, 这样每个promise对象,都可以注册函数.
所以问题变成了,
state=='pending' 时, 我们要返回什么promise对象?
这个对象并不需要设置 resolve,reject, 状态,
假设我们随便返回一个promise对象,
在原型的数组上注册了相应的函数,
但我们怎么去寻找呢?
应该是每个promise对象的 resolve/reject 对应相应的注册的函数.
根据对象的什么进行查找呢? 根据顺序嘛?
但这个对象和函数之间有可能不是一对一的关系, 而是一对多的关系.
所以这个数组应该是个二维数组?

假设我们在 state == 'pending'时, 返回了一个对象,
然后没有在原型上的数组中进行注册,而是在这个实例上进行了注册.
那我们要做的就是在then执行之后返回对象时,或者传递数据的时候, 只要在这个对象上操作就可以了?

怎么确定下一个promise对象是哪一个?
第一个思路是还是在 原型上定义一个数组,专门用来存放生成的promise对象,
然后按顺序执行即可.
但这么做似乎不妥,不妥在于,同一个promise对象可以并行执行多个then(),
返回的promise对象,应该都是不同的, 那这些对象是放在原型的同一个数组嘛?

第二个思路是,链表的思路,
我只需要在这个实例上标记好,我要返回的下一个实例是谁就可以了.

我们试一下? 虽然还是一团乱麻, 但可以试一下,
对了还有一个问题,
这个问题是, 按照上面这个逻辑讲,
实例对象应该在 then() 的时候先产生, 然后在resolve的时候返回.
可如果是同步代码,这就会出问题.
同步代码是, resolve 会比then先进行读取,因为,我们一般会把then写在后面.
解决方法是, 我们用 setTimeout() 让 resolve 运行缓一下?

一敲代码就发现问题了,
上面的思路行不通,
行不通的原因是,
说到底, 我是想在then时先创建对象, 然后在resolve时 返回对象.
问题在于, 数据必须在创建对象的时候传递,
而数据只有在resolve的时候才会得到.
也就是说 then时我生成了对象,但无法把数据传进去

不对,严格来讲还是可以改变data的, 只不过方法稍微暴力了一点.
不对是非常的暴力, 感觉非常不可取, 但我们先写一下,,

      class myPromise {
        constructor(fn) {
          if(typeof fn !== "function") {
            throw TypeError(`myPromise resolver ${fn} is not a function`)
          }
          this.state = "pending";
          this.data = undefined;
          
用来存放注册的函数
          this.resList = [];
          this.rejList = [];
          
          let resolve = (data) => {
            if(this.state == "pending") {
              this.state = "resolved"
              this.data = data;
 如果有注册的函数就执行注册的函数, 注意,这里要用传参的方式把数据传进去
              this.resList.forEach((item) => {
                item(this.data);
              })
            }
          }
          let reject = (data) => {
            if(this.state == "pending") {
              this.state = "rejected"
              this.data = data;
如果有注册的函数就执行注册的函数, 注意,这里要用传参的方式把数据传进去
              this.rejList.forEach((item) => {
                item(this.data);
              })
              
              
            }
          }
          这里是为了能够让 then在resolve,reject之前执行.
          主要是解决同步时候的问题.
          setTimeout(function () {
            fn(resolve, reject);
          },0)
        }
        
        then(resolveFn, rejectFn) {
          把函数放进相应的数组里.
          this.resList.push( (data) => {
         这里不能用 this.data 因为我们会有一次更换对象的操作.
            let rus = resolveFn(data);
            if(rus instanceof myPromise) {
          从这里开始就惨不忍睹了, 非常暴力
          把之前生成的对象里的 注册函数 暴力赋值
              rus.resList = this.next.resList;
              rus.next = this.next.next;
              return rus
            } else {
          用暴力的赋值的方式更改对象的状态和 数据
              this.next.state = "resolved";
              this.next.data = rus;
              this.next.resList.forEach((item) => {
                item(rus);
              })
            }
          })
            
            this.rejList.push((data) => {
                let rus = rejectFn(data);
            if(rus instanceof myPromise) {
              rus.rejList = this.next.rejList;
              rus.next = this.next.next;
              return rus
            } else {
              this.next.state = "rejected";
              this.next.data = rus;
              this.next.rejList.forEach((item) => {
                item(rus);
              })
            }
            })
            这里我们先生成了一个promise对象, 并且返回了出去.
            this.next = new myPromise((resolve,reject) => {});
            return this.next;
        }
        static resolve(data) {
          return new myPromise((suc) => {
            suc(data);
          })
        }
        static reject(data) {
          return new myPromise((suc, err) => {
            err(data);
          })
        }
      }

      let p = new myPromise((suc, err) => {
        setTimeout(function () {
            err('suc');
        },2000);
      });

      p.then(null, (data) => {console.log(data);
        return new myPromise((res,rej) => {
          setTimeout(function () {
            rej('err');
          },1000)
        })
        
      }).then(null,data => {console.log(data);return 223})
      .then(null,data => console.log(data))

貌似整个能够模拟今天所学的内容.
但异常暴力,感觉问题很大很大..
不过思考两个小时,能够坚持思考,并敲一敲,还是挺欣慰的.

明天再看视频.

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

推荐阅读更多精彩内容

  • Promise 对象 Promise 的含义 Promise 是异步编程的一种解决方案,比传统的解决方案——回调函...
    neromous阅读 8,697评论 1 56
  • title: promise总结 总结在前 前言 下文类似 Promise#then、Promise#resolv...
    JyLie阅读 12,215评论 1 21
  • 本文适用的读者 本文写给有一定Promise使用经验的人,如果你还没有使用过Promise,这篇文章可能不适合你,...
    HZ充电大喵阅读 7,293评论 6 19
  • 一、Promise的含义 Promise在JavaScript语言中早有实现,ES6将其写进了语言标准,统一了用法...
    Alex灌汤猫阅读 817评论 0 2
  • 我走过人来人往 不喜欢也得欣赏 天空灰蒙 路和远方 我在新疆公路 你在哪里? 黑黑的天空低垂 亮亮的繁星相随 你在...
    OwOwww阅读 357评论 0 0