• Promise原理剖析


    传统的异步回调编程最大的缺陷是:回调地狱,由于业务逻辑非常复杂,代码串行请求好几层;并行请求以前也要通过引用step、async库实现。现在ES6推出了Promise,通过Promise的链式调用可以解决回调地狱问题,通过Promise.all方法可以解决并行请求的问题。现在我们通过手写Promise来彻底理解Promise的原理。
     一、构造简单Promise函数,实现Promise的then方法
    先来看一下简单的使用方法:
    var promise=new Promise(function(resolved,rejected){
      console.log(1);
      resolved('123');
    })
    promise.then(function(data){
      console.log('success'+data)
    },function(err){
      console.log('fail'+err)
    })
    

      

    注意点:
    1. new Promise传入的executor方法代码是同步执行的;
    2. promise对象的状态有三种:pending(等待态),resolved(成功态),rejected(失败态),只能从等待态转化成成功态或失败态;
    3. executor中执行resolve()方法,表示将转化为成功态,promise.then调用时执行成功的方法,executor中执行reject()方法,表示将转化为成功态,promise.then调用时执行失败的方法。
    思路:
    1. 首先构造一个类Promise,传入一个参数executor方法;
    2. 用status记录Promise状态,默认是pending,成功态是resolved,失败态是rejected, execuotor执行时,重写resolve、reject方法,执行到这两个方法时,将promise状态修改,并将参数存储起来,以便then方法调用;
    3.当实例执行then方法时,依据promise状态来执行成功方法或失败方法。
    这样就实现了这个简单的peomise,代码如下:
    function Promise(executor){
      let self=this;
      self.status='pending';
      self.value=undefined;
      self.reason=undefined;
    function resolve(value){
      if(self.status==='pending'){
        self.value=value;
        self.status='resolved';
      }
    }
    function reject(reason){
      if(self.status==='pending'){
        self.reason=reason;
        self.status='rejected';
      }
    }
      try{
        executor(resolve,reject) //第一步:先执行executor,根据里面resolve和reject的调用,执行上面的resolve和reject方法
      }catch(e){
        reject(e);
      }
    }
    Promise.prototype.then=function(onFulfilled,onRejected){
      let self=this;//第二步:then被调用的时候根据已经记录的promise状态,执行成功方法或失败方法
      if(self.status==='resolved'){
        onFulfilled(self.value);
      }
      if(self.status==='rejected'){
        onRejected(self.reason);
      }
    }
    module.exports = Promise
    二、实现Promise executor中的异步调用
    先来看一下简单使用小例子:
    var promise=new Promise(function(resolve,reject){
        console.log('hello')
        setTimeout(function(){
            resolve('ok')
        },1000);
    })
    promise.then(function(data){
        console.log('success'+data)
    },function(err){
        console.log('fail'+err)
    })        
    上面例子,先打印出hello,过1秒后执行resolve方法,打印出successok。
    注意点:
    1. executor执行一秒之后再调用resolve方法,才会执行then中的成功方法;
    思路:
    1. executor中方法没调用resolve之前,Promise方法一直是pending状态,这时候执行器已经执行完了then方法,这时我们把then方法中的成功方法和失败方法存入数组,当resolve方法调用时,去执行这些存储起来的方法;
    2. 没有异步调用的话,executor执行时执行resolve方法,成功数组 为空,所以不会被影响,还是在then方法中去执行成功或失败方法。
     
    function Promise(executor){
      let self=this;
      self.status='pending';
      self.value=undefined;
      self.reason=undefined;
      self.onResolvedCallbacks=[];
      self.onRejectedCallbacks=[];
      function resolve(value){
        if(self.status==='pending'){
          self.value=value;
          self.status='resolved';
          self.onResolvedCallbacks.forEach(item=>item());//最终执行resolve方法时,修改状态,将成功数组中的方法取出,一一执行。
        }
      }
      function reject(reason){
        if(self.status==='pending'){
          self.reason=reason;
          self.status='rejected';
          self.onRejectedCallbacks.forEach(item=>item());//最终执行reject方法时,修改状态,将失败数组中的方法取出,一一执行。
        }
      }
      try{
        executor(resolve,reject)
      }catch(e){
        reject(e);
      }
    }
    Promise.prototype.then=function(onFulfilled,onRejected){
      let self=this;
      if(self.status==='resolved'){
        onFulfilled(self.value);
      }
      if(self.status==='rejected'){
        onRejected(self.reason);
      }
      if(self.status==='pending'){ //执行then的时候是等待态,就将成功方法存入onResolvedCallbacks数组,将失败方法存入onRejectedCallbacks数组
        self.onResolvedCallbacks.push(function(){
          onFulfilled(self.value);
        });
        self.onRejectedCallbacks.push(function(){
          onRejected(self.reason);
        });
      }
    }
    module.exports = Promise
    三、实现Promise then的链式调用
    链式调用是Promise中的核心方法,也是最重要的一个方法,先来看一下链式调用的用法:
    let p=new Promise((resolve,reject)=>{
      console.log(1);
      setTimeout(function(){
        resolve('123');
      },1000);
    })
    let p2=p.then((data)=>{
      console.log(data);
      return new Promise((resolve,reject)=>{
        setTimeout(function(){
          resolve('456');
        },1000);
      });
    },err=>{
      console.log(err);
      throw Error('失败');
    }).then((data)=>{
      console.log('aaa'+data);
    },(err)=>{
      console.log(err);
    })
    上面代码首先打印出executor中的1,1秒之后执行resolve方法执行第一个then中的成功方法,先打印出123,再执行此方法中return的Promise,一秒之后执行下一个then的成功方法,打印出aaa456。
    注意点:
    1. 如果then方法返回一个普通值,执行下一个then的成功方法;
    2. 如果抛出一个错误,执行下一个then的失败方法;
    3. then方法中返回一个Promise对象,等待Promise对象执行成功就调用下个then的成功方法,这个Promise调用reject()就执行下一个then的失败方法;
    思路:
    1.在then方法后之所以可以接着调用then方法,肯定then方法需要返回一个Promise实例;
    2. 在then方法中我们用x去接收成功或失败方法的返回值,当成功或失败方法抛出错误,直接执行reject,调用下一个then的失败方法,专门写了一个方法resolvePromise分析返回值;
    3. resolvePromise方法中传入了四个参数,then中的Promise2,x值;
    4. 如果x与promise相等,报类型错误循环引用;
    5. 当x不是null,是个对象类型或者是个函数类型,并且他的then方法也是函数类型时,我们认为x是一个promise实例,执行x.then()方法;再用resolvePromise方法分析他的成功的返回值;
    6. 如果x不是null不是object或function,我们认为x是一个普通值,执行promise2中的resolve方法;
    7. 如果x.then方法获取失败,调用promise2的reject方法;
    8. 定义一个called为true,当执行过resolve或reject方法后将其变为false,保证resolve和reject方法只执行一次;
    9. 这里面存在两个递归调用,一个是then方法里面return new Promise方法,一个是resolvePromise方法,第一个的递归调用解决了executor中异步调用的问题,第二个递归调用解决了return Promise实例的问题。



    代码如下:
     
    function Promise(executor){
      let self=this;
      self.status='pending';
      self.value=undefined;
      self.reason=undefined;
      self.onResolvedCallbacks = []; // 存放then成功的回调
      self.onRejectedCallbacks = []; // 存放then失败的回调
     
      function resolve(value){
        if(self.status==='pending'){ //这里面的self不能用this替代,因为是回调执行的时候不是在类里面所以这里的this不是类函数
          self.status='resolve';
          self.value=value;
          self.onResolvedCallbacks.forEach(item=>{item()})
        }
      }
      function reject(reason){
        if(self.status==='pending'){
          self.status='reject';
          self.reason=reason;
          self.onRejectedCallbacks.forEach(item=>{item()})
        }
      }
      try{
        executor(resolve,reject);
      }catch(e){
        reject(e);
      }
    }
    function resolvePromise(promise2,x,resolve,reject){
      if(promise2===x){//自己等于自己,报类型错误
        return reject(new TypeError('循环引用了'));
      }
      let called;//控制成功或失败只能调用一次
      if(x!==null && (typeof x==='object'||typeof x==='function')){
        try{
          let then=x.then;
          if(typeof then==='function'){
            then.call(x,function(y){ //y可能还是一个promise,递归解析,直到返回普通值
              if(called)return;
              called=true;
              resolvePromise(promise2,y,resolve,reject)
            },function(err){
              if(called)return;
              called=true;
              reject(err);
            });
          }else{
            resolve(x);
          }
        } catch (e) {
          if (called) return
          called = true;
          reject(e);
        }
      } else { // 说明是一个普通值
        resolve(x);
      }
    }
    Promise.prototype.then=function(onFufilled,onReject){
      onFufilled=typeof onFufilled==='function'?onFufilled:function(value){
        return value;
      }
      onReject=typeof onReject==='function'?onReject:function(err){
        throw err;//抛出错误,才会走下个then的失败
      }
      let self=this;
      let promise2;
      if(self.status=='pending'){
        promise2=new Promise(function(resolve,reject){
          self.onResolvedCallbacks.push(function(){
            setTimeout(function(){
              try{
                let x=onFufilled(self.value);
                resolvePromise(promise2,x,resolve,reject);
              }catch(e){
                reject(e)
              }
            })
          });
          self.onRejectedCallbacks.push(function(){
            setTimeout(function(){
              try{
                let x=onReject(self.reason);
                resolvePromise(promise2,x,resolve,reject);
              }catch(e){
                reject(e);
              }
            })
          });
        })
      }
      if(self.status=='resolve'){
        promise2=new Promise(function(resolve,reject){
        setTimeout(function(){
          try{
            let x=onFufilled(self.value);
            resolvePromise(promise2,x,resolve,reject);
          }catch(e){
            reject(e)
          }
        })
      })
    }
      if(self.status=='reject'){
        promise2=new Promise(function(resolve,reject){
          setTimeout(function(){
            try{
              let x=onReject(self.reason);//这里的x可能是一个普通值,也可能是一个promise,所以写个方法统一处理。
              resolvePromise(promise2,x,resolve,reject);
            }catch(e){
              reject(e);
            }
          })
        })
      }
      return promise2;
    };
    四、检测一下我们的Promise是否符合Promise A+规范
    我们需要先给Promise类增加一个defer方法,其实就是一个语法糖。
    Promise.defer = Promise.deferred = function () {
        let dfd = {};
        dfd.promise = new Promise(function (resolve, reject) {
            dfd.resolve = resolve;
            dfd.reject = reject;
        });
        return dfd
    }            
     
    没有上面的语法糖,无法被检测。
    下载一个插件npm install -g promises-aplus-tests
    在命令行工具中执行命令:npm install -g promises-aplus-tests ./promise.js
    即可检验自己的promise写的如何。
    五、附赠Promise的all、race、resolve、reject、catch方法
     
    Promise.all=function(promises){
    //promises是一个promise的数组,同时需要返回一个promise
      return new Promise(function(resolve,reject){
        let arr=[]; //arr是最终返回值的结果集
        let i=0;
        function processData(index,y){
          arr[index]=y;
          if(++i===promises.length){
            resolve(arr);
          }
        }
        for(let i=0;i<promises.length;i++){ //因为then的时候,for循环已经走了,i值就是最大的,所以这里用let,保证每次i都是都是当前值
          promises[i].then(function(y){
            processData(i,y);
          },function(err){
            reject();
          })
        }
      })
    }
    Promise.race=function(promises){
      return new Promise(function(resolve,reject){
        for(let i=0;i<promises.length;i++){
          promises[i].then(function(data){
            resolve(data);
          },function(err){
            reject(err);
          })
        }
      })
    }
    Promise.resolve=function(value){
      return new Promise(function(resolve,reject){
        resolve(value);
      })
    }
    Promise.reject=function(reason){
      return new Promise(function(resolve,reject){
        reject(reason);
      })
    }
     
    Promise.prototype.catch = function (callback) {
      return this.then(null, callback)
    }
  • 相关阅读:
    Linux下一些命令
    Javascript实现Base64解码
    C语言中指针和数组的区别
    hdu 1166 线段树 解法 484MS
    fatjar: eclipse导出工具
    每天一道面试题(1)~~~
    windows Emacs的安装与配置
    编程者入门:谈Servlet/JSP的配置详解
    RMQ谈:Range Minimum/Maximum Query 区间最小/大值查询 ST优化
    Lisp in a box 装配指南
  • 原文地址:https://www.cnblogs.com/learnings/p/9392481.html
Copyright © 2020-2023  润新知