最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 手写Promise-符合MDN描述和promise/A+规范

    正文概述 掘金(keep_curious)   2021-01-05   628

    为什么还要实现promise???,因为现有的手写方案,有的不符合MDN的描述、有的不符合promise/A+规范、有的不能兼容同步和异步两种内部函数的使用形式。故此,还是自己动手、丰衣足食,实现了PromiseDemo,以下是测试用例及实现源码

    以下测试用例均已通过:

    // 用例1:同步情况下resolve

    new PromiseDemo(function(resolve, reject){
    
      resolve('同步')
    
    }).then(function(res) {
    
      console.log(res); // 同步
    
    });  
    

    // 用例2:resolve为promise时

    new PromiseDemo(function(resolve, reject){
    
      resolve(new PromiseDemo(function(reso) {reso('resolve-promose')}))
    
    }).then(function(res) {
    
      console.log(res); // resolve-promose
    
    }); 
    

    // 用例3:错误捕捉

    new PromiseDemo(function(resolve, reject){
    
      throw new Error('错误');
    
      resolve(123)
    
    }).then(function(res) {
    
      console.log(res);
    
    }).catch(function(err) {
    
      console.log(err);
    
    });  
    // Error: 错误
    // at <anonymous>:2:9
    // at new PromiseDemo (<anonymous>:9:13)
    // at <anonymous>:1:1  
    

    // 用例4:异步resolve

    new PromiseDemo(function(resolve, rej) {
    
      setTimeout(function() {
    
    ​    resolve(1);
    
      }, 10)
    
    }).then(function(res) {
    
      console.log(res); // 1
    
    }, function(err) {
    
      console.log(err);
    
    });  
    

    // 用例5:all方法

    PromiseDemo.all([
      fetch('https://cdn.bootcss.com/vue/2.5.16/vue.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(6,21)),
      fetch('https://unpkg.com/react@16/umd/react.production.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(13,27)),
    ]).then(function(res) {
      console.log(res); // [" Vue.js v2.5.16", "React v16.14.0"]
    });  
    

    // 用例6:race方法请求不同资源

    PromiseDemo.race([
    
      fetch('https://cdn.bootcss.com/vue/2.5.16/vue.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(6,21)),
    
      fetch('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', {method: 'GET'}).then((t) => t.text()).then((r) => r.slice(3,17))
    
    ]).then(function(res) {
    
      console.log(res); // Vue.js v2.5.16
    
    });  
    

    // 用例7:race方法请求不同资源

    PromiseDemo.race([
    
      fetch('https://unpkg.com/react@16/umd/react.production.min.js', {method: 'GET'}).then((r) => r.text()).then((r) => r.slice(13,27)),
    
      fetch('https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js', {method: 'GET'}).then((t) => t.text()).then((r) => r.slice(3,17))
    
    ]).then(function(res) {
    
      console.log(res); // jQuery v3.4.1
    
    });  
    

    PromiseDemo实现源码

    感兴趣的同学,建议观码顺序:

    1.constructor

    2.resolve

    3.then

    4.reject

    5.catch

    6.finally

    7.all

    8.race

    根据MDN描述,resolve、reject、all、race为静态方法,所以写了static

    根据Promise/A+规范,resolve的参数可能是promise,故在resolve内部还要判断是否是PromiseDemo的实例,再进行逻辑拆分。

    class PromiseDemo {
      
      /**  构造函数
       *   初始化then事件队列、catch事件队列、状态status
       *   执行参数fn,传入resolve和reject
      */  
    
      constructor(fn) {
        if (fn && typeof fn !== 'function') throw new Error(`Parameter is not a function`);
        this.thenQue = []
        this.catchQue = []
        this.status = '<pending>';
        try {
          fn && fn(PromiseDemo.resolve.bind(this), PromiseDemo.reject.bind(this));
        } catch(e) {
          PromiseDemo.reject.bind(this)(e);
        }
      }  
    
    
      /**  resolve函数
       *   主要作用为执行thenQue队列中的函数
       *   如果状态为fulfilled,则不再执行
       *   如果resolve的参数不是PromiseDemo实例,则正常执行,这也是我们常用的场景
       *   如果resolve的参数是PromiseDemo实例,则将实例内部的resolve值或者then内的函数返回值暴露出来,连接上外部的resolve执行,这样可以接着用外部的then函数队列,依次执行。  *   如果resolve一开始调用时,没有值,则返回一个PromiseDemo实例,因为存在用法Promise.resolve().then()
       *   函数内使用了setTimeout,是为了将后续逻辑加入下一个宏任务,此时,then将优先执行,提前将函数逻辑加入thenQue队列
       *   注意,promise是微任务,此处用setTimeout是为了实现promise效果,因为浏览器环境下,除了mutation和promise外,没有可以异步的函数了。
      */  
    
      static resolve(data) {
        if (this.status === '<fulfilled>') return;
        this.value = data;
        const isInstance = data instanceof PromiseDemo;
        if (!isInstance) {
          setTimeout(() => {
            try {
              this.thenQue.forEach((item) => {
                this.value = item(this.value);
              });
              this.thenQue = [];
              this.status = '<fulfilled>';
            } catch (e) {
              this.status = '<rejected>';
              PromiseDemo.reject.bind(this)(e);
            }
          });
        } else {
          data.then((res) => {
            PromiseDemo.resolve.bind(this)(res);
          }).catch((err) => {
            PromiseDemo.reject.bind(this)(err);
          });
        }
        if (!data) {
          return new PromiseDemo();
        }
      }  
        
      /**  reject函数
       *   主要作用是执行catchQue事件队列中的catch事件函数
      */  
    
    
      static reject(err) {
        if (this.status === '<rejected>') return;
        this.error = err;
        let count;
        setTimeout(() => {
          try {
            this.catchQue.forEach((item, index) => {
              count = index;
              this.error = item(this.error);
            });
            this.catchQue = [];
            this.status = '<rejected>';
          } catch (e) {
            this.catchQue = this.catchQue.slice(count+1);
            PromiseDemo.reject.bind(this)(e);
          }
        });
        if (!err) {
          return new PromiseDemo();
        }
      }  
        
      /**  then函数
       *   主要作用为将then内的函数全部收集起来,组成then事件队列thenQue
      */  
    
    
      then(onResolve, onReject) {
        if (typeof onReject === 'function') {
          this.catchQue.push(onReject);
        }
        typeof onResolve === 'function' && this.thenQue.push(onResolve);
        return this;
      }  
        
      /**  catch函数
       *   主要作用为将catch内的函数全部收集起来,组成catch事件队列catchQue
      */  
    
    
      catch(fn) {
        this.catchQue.push(fn);
        return this;
      }  
    
      /**  finally函数
       *   将fn推入队列,无论事件队列thenQue执行,还是catchQue执行,最后都可以执行到
      */  
    
      finally(fn) {
        this.thenQue.push(fn);
        this.catchQue.push(fn);
      }  
        
      /** all函数
       *   参数为数组,数组每一项都是PromiseDemo的实例
       *   对每项添加then方法,则当执行到then内部方法时,判断是否全部promise都已执行完,若都已执行完毕,则整体resolve
      */  
    
    
      static all(arr) {
        const resArr = [];
        const length = arr.length;
        let resCount = 0;
        let that;
        try {
          arr.forEach(function(item, index) {
            item.then(function(res) {
              resArr[index] = res;
              resCount++;
              if (resCount === length) {
                PromiseDemo.resolve.bind(that)(resArr);
              }
            })
          });
        } catch (e) {
          PromiseDemo.reject.bind(that)(e);
        }
        that = new PromiseDemo();
        return that;
      }  
        
    
      /**  race函数
       *   只要有一个执行完毕,则直接整体resolve,当另一个也执行到resolve时,因status已发生改变,则不会再向下执行
      */
    
      static race(arr) {
        let that;
        try {
          arr.forEach(function(item, index) {
            item.then(function(res) {
              PromiseDemo.resolve.bind(that)(res);
            })
          });
        } catch (e) {
          PromiseDemo.reject.bind(that)(e);
        }
        that = new PromiseDemo();
        return that;
      }
    
    }
    
    

    起源地下载网 » 手写Promise-符合MDN描述和promise/A+规范

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元