ES6系列教程第一篇--Promise详解

一、为什么要用Promise

     在实际项目中,有下面应用场景,在手机上浏览电影网站,其中有个海报墙页面,里面有海量的电影节目,如下图所示。考虑到性能和用户体验,启动后,我们需要串行的加载10页数据(每页9张海报),即第一页加载完成后,启动第二页的加载,以此类推。

于是不假思索的写下了下面的代码:

$(document).ready(function(){
      //获取第一页数据
      $.getJSON("json/poster.json?page=1",function(result){
       attachPoster(result);
       //获取第二页数据
       $.getJSON("json/poster.json?page=2",function(result){
          attachPoster(result);
          //获取第三页数据
          $.getJSON("json/poster.json?page=3",function(result){
            attachPoster(result);
             ...
          });
      });
     });
    });  

一直写到自己恶心,这就是叫做"回调地狱"。

是否有解?有,那就是Promise

二、Promise是什么

    还用上面的例子,改成下面的写法:

function getPoster(page){
      const promise = new Promise(function(resolve,reject){
        $.getJSON("json/poster.json?page="+page,function(result){
          resolve(result);
        })
      });
      return promise;
    }
    getPoster(1).then(function(result){//获取第一页
      attachPoster(result); 
      return getPoster(2);
    }).then(function(result){//获取二页
      attachPoster(result); 
      return getPoster(3);
    }).then(funciton(result){//获取第三页 ...})

   道友们先不用理会Promise的细节,从代码结构上看,比第一种的层层嵌套是不是更清晰,更符合逻辑。Promise就是为了解决回调函数嵌套的一种解决方案。

三、then与resolve

通过下面例子初步了解Promise是怎么玩的。

function getPObj(){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器");
         resolve("执行回调方法");
       },2000);
      });
      return p;
    }
 getPObj();

在getPObj中我们new了Promise对象p并返回该对象,在构造p对象的方法中,只有一个定时器,2s钟后打印一个日志和执行resolve入参方法。

我们调用getPObj执行下,结果如下:

只是执行了日记,并没有看到resolve方法的执行,这也不奇怪,因为resolve作为构造函数的入参,我们根本就没有定义。

我们将代码改成下面

function getPObj(){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器");
         resolve("执行回调方法");
       },2000);
      });
      return p;
    }
    getPObj().then(function(data){
       console.log("我是回调方法");
       console.log(data);
    });

再次此时的执行结果:

    then的入参函数,就是resovle的回调方法。看到这里,大家可能会问,这不就是个callback作为入参的回调么,只不过用了then的属性方法传入的,一种表示方式而已,有啥稀奇的。如果只是一层嵌套是看不出优越性,还记得我们前面海报加载的场景么,如果嵌套多层,then的链式调用就发挥巨大优先性了,它能把层层嵌套平铺开来。

我们将上面的实例再改造下:

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
         resolve(num);
       },2000);
      });
      return p;
    }
    getPObj(1).then(function(data){
       console.log("我是回调方法");
       console.log("执行回调方法:"+data);
       return getPObj(2);
    }).then(function(data){
       console.log("我是回调方法");
       console.log("执行回调方法:"+data);
       return getPObj(3);
    }).then(function(data){
       console.log("我是回调方法");
       console.log("执行回调方法:"+data);
    });

在每个回调执行完成后,再返回一个新的Promise对象,继续下一次操作。

回过头来看我们开篇讲到海报加载的例子,到此可以理解了。

四、reject

细心的道友可能发现,在Promise对象的构造方法的入参中,还有个reject方法我们还没有讲到。

const promise = new Promise(function(resolve,reject){  
       somethingDO();  
       if (/*结果符合预期,异步操作成功*/) {  
            resolve()  
       }else/*不符合预期,操作失败*/  
       {  
           reject();  
       }  
    })

    Pomise有三种状态,分别是pending(进行中),resolved(已成功),rejected(已失败),一旦达到相应的状态,就会回调相应的方法。其实称作已成功,或者已失败并不准确,ES6中标准说法fullfiled,rejected。至于什么是已成功状态,什么是已失败状态,可以自己按照实际情况自定义。

    对应的,then方法有两个入参,分别实现resolved,rejected的回调方法。

promise.then(function(value) {
  // resolved
}, function(error) {
  // rejected
});

继续上面的实例,我们在方法增加控制,生成一个1-10的随机方法,如果大于5就表示失败。

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数
          if (i<5) {
           resolve(num);
         }else{
           reject(num);
        }
        
      },2000);
      });
      return p;
    }

    getPObj(1).then(function(data){
      console.log("执行回调方法:"+data);
      return getPObj(2);
     },function(data){
      console.log("执行回调方法失败:"+data);
    }).then(function(data){
      console.log("执行回调方法:"+data);
      return getPObj(3);
     },function(data){
      console.log("执行回调方法失败:"+data);
     }).then(function(data){
      console.log("执行回调方法:"+data);
     },function(data){
      console.log("执行回调方法失败:"+data);
    });

执行的结果:

   第一次执行时,i的随机值就大于5,所以执行了rejected的方法。但是和我们预期的还是有点不一样,如果返回失败,我们希望终止掉整个链条,但是从实际结果看,是继续往下执行。这是因为,回调第一个reject的方法后,没有返回值,Promise会自动返回一个undefined,传入下一个链条的resolve方法中,并继续后面的then链。

  有没有方法,一旦执行失败,就中断后面的then链条呢?有,各位继续往下。

五、catch

try...catch我们常用捕获异常的方法,在promise对象中也有catch的方法。用来捕获then回调方法中抛出的各类异常,用法如下:

p.then(function(){
         ...
}).catch(e){
          ....
}

现在我们用上面的实例构造一个异常。

 function getPObj(num){  
      var p = new Promise(function(resolve,reject){  
        setTimeout(function(){  
         console.log("开始执行定时器:"+num);  
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数  
          if (i<5) {  
           resolve(num);  
         }else{  
          reject(num);  
        }   
      },2000);  
      });  
      return p;  
    }  
    getPObj(1).then(function(data){  
      console.log("执行回调方法:"+data);  
      //x没有定义,抛出异常  
      x+2;  
     }).catch(function(e){  
       console.log(e);  
     })  

执行的结果如下:

捕获并打印了异常。

我们来解答第四章节提的问题,利用catch可终止then链条。如下

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数
          if (i<5) {
           resolve(num);
         }else{
          //构造一个error作为入参
          reject(new Error("Error"));
        } 
      },2000);
      });
      return p;
    }
     
    getPObj(1).then(function(data){
      console.log("执行回调方法:"+data);
      return getPObj(2);
     }).then(function(data){
      console.log("执行回调方法:"+data);
       return getPObj(3);
     }).then(function(data){
      console.log("执行回调方法:"+data);
     }).catch(function(e){
        console.log(e);
    });

reject构造一个error的入参,抛出异常,为catch捕获。从执行结果看,后面的then的没有执行,达到目的。

  道友们看到这个实例中,每个then的reject的方法都删除了,catch方法实际就是实现了全局的reject方法。在实际开发中,我们建议采用catch代替reject

六、finally

try...catch...finally是黄金组合,做过java开发的道友们对肯定非常熟悉。finally表示无论什么状态,必定都会执行。

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数
          if (i<5) {
           resolve(num);
         }else{
          reject(new Error("出错了"));
        } 
      },2000);
      });
      return p;
    }
     
    getPObj(1).then(function(data){
      console.log("执行回调方法:"+data);
      return getPObj(2);
     }).then(function(data){
      console.log("执行回调方法:"+data);
       return getPObj(3);
     }).then(function(data){
      console.log("执行回调方法:"+data);
     }).catch(function(e){
        console.log(e);
    }).finally(function(){
      console.log("finally");
    });

执行结果:

七、all、race

1、all

    Promise.all可以将几个Promise对象封装成一个,格式如下:

Promise.all([p1,p2,p3]).then(function(data){...})

当这几个对象都变成resolved状态后,总状态变为resolved;否则,其中有一个为rejected状态,则变成reject,其他的可以忽略。可以理解为p1&&p2&&p3。

    那返回的data是什么样子,如果是resolved状态,则是各个对象data的组合;如果是rejected,则是第一个到达rejected状态返回的data值。以例为证。

都为resolved状态:

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
         resolve(num);
      },2000);
      });
      return p;
    }

    Promise.all([getPObj(1),getPObj(2),getPObj(3)]).then(function(data){
            console.log("resolve");
            console.log(data);
         }).catch(function(e){
             console.log("error");
             console.log(e);
   })

执行的结果:

其中有一个返回rejected状态:

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数
          if (i<5) {
           resolve(num);
         }else{
          reject(num);
        } 
      },2000);
      });
      return p;
    }

    Promise.all([getPObj(1),getPObj(2),getPObj(3)]).then(function(data){
            console.log("resolve");
            console.log(data);
         }).catch(function(e){
             console.log("error");
             console.log(e);
   })

执行结果:

2、race

race与all类似,页可以将几个Promise对象封装成一个,格式如下:

Promise.race([p1,p2,p3]).then(function(data){...})

不同的时,看谁执行的快,then就回到回调谁的结果。可以理解为p1||p2||p3

看实例:

function getPObj(num){
      var p = new Promise(function(resolve,reject){
        setTimeout(function(){
         console.log("开始执行定时器:"+num);
          var i = Math.ceil(Math.random()*10); //生成1-10的随机数
          if (i<5) {
           resolve(num);
         }else{
          reject(num);
        } 
      },2000);
      });
      return p;
    }

    Promise.race([getPObj(1),getPObj(2),getPObj(3)]).then(function(data){
            console.log("resolve");
            console.log(data);
         }).catch(function(e){
             console.log("error");
             console.log(e);
         })

执行结果(由于随机数,各位执行的结果会有不一样的情况):

当接受到第一个对象的resolved状态后,其他的两个抛弃处理。

八、总结

   本文主要阐述了Promise的基本知识,在实际项目,有很多已经分装好的库可以使用,如q.js,when.js,jquery.deferred等,万变不离其踪,只要了解了基本的原理,这些库使用起来也会得心用手。

下一篇:ES6系列教程第二篇--Iterator 详解

发布了33 篇原创文章 · 获赞 95 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/tcy83/article/details/80274772