ES6: Promise

来源:互联网 发布:轻松抠图软件 编辑:程序博客网 时间:2024/05/18 01:38

在实际的使用当中,有非常多的应用场景我们不能立即知道应该如何继续往下执行。最重要也是最主要的一个场景就是ajax请求。通俗来说,由于网速的不同,可能你得到返回值的时间也是不同的,这个时候我们就需要等待,结果出来了之后才知道怎么样继续下去。

// 简单的ajax原生实现var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';var result;var XHR = new XMLHttpRequest();XHR.open('GET', url, true);XHR.send();XHR.onreadystatechange = function() {    if (XHR.readyState == 4 && XHR.status == 200) {        result = XHR.response;        console.log(result);    }}

在ajax的原生实现中,利用onreadystatechange时间,当该事件触发并且符合一定条件时,才能拿到我们想要的数据,之后我们才能处理数据。

这样做看上去并没有什么麻烦,但是如果这个时候,我们还要做另外一个ajax请求,这个新的ajax请求的其中一个参数,得从上一个ajax请求中获取,这个时候我们不得不如下这么做:

var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';var result;var XHR = new XMLHttpRequest();XHR.open('GET', url, true);XHR.send();XHR.onreadystatechange = function() {    if (XHR.readyState == 4 && XHR.status == 200) {        result = XHR.response;        console.log(result);        // 伪代码        var url2 = 'http:xxx.yyy.com/zzz?ddd=' + result.someParams;        var XHR2 = new XMLHttpRequest();        XHR2.open('GET', url, true);        XHR2.send();        XHR2.onreadystatechange = function() {            ...        }    }}

当出现第三个ajax(甚至更多)仍然一来上一个请求的时候,我们的代码就变成了一场灾难。这场灾难,往往也被称为回调地狱。
因此我们需要一个叫做Promise的东西,来解决这个问题。

当然除了回调地狱之外,还有一个非常重要的需求:为了我们的代码更加具有可读性和可维护性,我们需要将数据请求与数据处理明确的区分开来。上面的写法,事件处理程序完全没有区分开,当数据变得复杂是,也可我们自己就无法轻松维护好自己的代码了,这也是模块化中,必须要掌握的一个重要技能,请一定要重视。
当我们确保某代码在谁睡之后执行,我们可以利用函数调用栈,将我们想要执行的代码放入回调函数中。

//一个简单的封装function want() {    console.log('这是你想要执行的代码');}function fn(want){    console.log('这里表示执行了一大堆代码');    //其它代码执行完毕,最后执行回调函数    want&&want();}fn(want);

利用回调函数封装,使我们初学JavaScript时常常会使用的技能。
确保我们想要的代码后执行,除了利用函数调用栈的执行顺序之外,我们还可以利用队列机制。

function want() {    console.log('这是你想要执行的代码');}function fn(want){    //将想要执行的代码放入队列中,根据时间循环的机制,我们就不用非得将它放到最后面了,有你自由选择。    want&&setTimeout(want,0);    console.log('这里表示执行了一大堆各种代码');}fn(want);

如果浏览器已经支持了原生的Promise对象,那么我们就知道,浏览器的js引擎里已经有了Promise队列,这样就可以利用Promise将任务放到它的队列中去。

function want(){    console.log('这是你想要执行的代码');}function fn(want){    console.log('这里表示执行了一大堆各种代码');    return new Promise(function(resolve,reject){        if(typeod want == 'function'){            resolve(want);        }else{            reject('TypeErro'+want+'不是一个函数');        }    });}fn(want).then(function(want){    want();})fn('1234').then(function(err){    console.log(err);})

看上去更复杂了。可是代码变得更加健壮,处理输入错误的情况。

为了更好的往下扩展Promise的应用,这里需要先跟大家介绍一下Promise的基础知识。
一、Promise对象的三种状态,他们分别是:

  1. pending:等到中,或者进行中,表示还没有得到结果
  2. resolved(Fulfilled):已经完成,表示得到了我们想要的结果,可以继续往下执行
  3. rejected:也表示得到结果,但是结果并非我们所愿,所以拒绝执行

这三种状态不受外界影响,而且状态只能从pending改变为resolved或者rejected,并且不可逆。在Promise对象的构造函数中,将一个函数作为第一个参数,而这个函数就是用来处理Promise状态变化。

new Promise(function(resolve,reject){    if(true){resolve()}    if(false){reject()}})

上面的resolve和rejecte都为一个函数,他们的作用分别试讲状态改为resolved和rejected。

二、Promise对象的then方法,可以接受了构造函数中处理的状态变化,并分别执行,then方法有两个参数,第一个参数接受resolved状态的执行,第二个参数接受rejecte状态的执行。

function fn(num){    return new Promise(function(resolve,reject){        if(typeof num == 'number'){            resolve();        }else{            reject();        }    }).then(function(){        console.log('参数是一个number值');    },function(){        console.log('参数不是一个number值');    })}fn('hahaha');       //参数不是一个number值fn(1234);           //参数是一个number值

then方法的执行结果也会返回一个Promise对象,因此我们可以进行痛恨的链式执行,这也是解决回调地狱的主要方式。

function fn(num){    return new Promise(function(resolve,reject){        if(typeof num == 'number'){            resolve();        }else{            reject();        }    }).then(function(){        console.log('参数是一个number值');    }).then(null,function(){        console.log('参数不是一个number值');    })}fn('hahaha');       //参数不是一个number值fn(1234);           //参数是一个number值

then(null,function(){})就等同于catch(function(){})

三、Promise中的数据传递
大家从下面的例子中领悟吧

var fn = function(num) {return new Promise(function(resolve,reject){        if (typeof num == 'number') {            resolve(num);        }else {            reject(num);        }    })}fn(2).then(function(num){    console.log('first:' + num);    return num+1;}).then(function(num){    console.log('second:' + num);    return num+1;}).then(function(num){    console.log('third:'+ num);    return num+1;})//输出结果//first:2//second:3//third:4

note:这里then执行结果返回的值作为下一个then的参数输入
了解了这些基础知识,再回过头,利用Promise的知识,对最开始的ajax例子进行一个简单的封装,看看会是什么样子。

var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';        //封装一个get请求的方法        function getJSON(url) {            return new Promise(function(resolve,reject){                var XHR = new XMLHttpRequest();                XHR.open('get',url,true);                XHR.send();                XHR.onreadystatechange = function() {                    if (XHR.readyState == 4) {                        if (XHR.status == 200) {                            try {                                var response = JSON.parse(XHR.responseText);                                resolve(response);                            }catch(err){                                reject(err);                            }                        }else {                            reject(new Error(XHR.responseText));                        }                    }                }            })        }        getJSON(url).then(resp=>console.log(resp));

为了健壮性,处理了很多可能出现的异常,总之,就是正确返回结果,就resolve一下,错误的返回结果,就reject一下。并且利用上面的参数传递的方式,将正确结果或者错误信息通过他们的参数传递过来。

现在所有的库几乎都将ajax请求利用promise进行了封装,因此我们在使用jquery等库中的ajax请求时,都可以利用Promise来让我们的代码更加优雅和简单。这也是Promise最常用的一个场景,因此我们一定要非常非常熟悉它,这样才能在应用的时候更加灵活。

四、Promise.all
当有一个ajax请求,它的参数需要另外2个甚至更多请求都有返回结果之后才能确定,那么这个时候,就需要用到Promise.all来帮助我们应对这个场景。
Promise.all接收一个Promise对象组成的数组作为参数,当这个数组所有的Promise对象状态都变成resolved或者rejected的时候,他才会去调用then方法。

var url = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-02-26/2017-06-10';var url1 = 'https://hq.tigerbrokers.com/fundamental/finance_calendar/getType/2017-03-26/2017-06-10';function renderAll() {    return Promise.all([getJSON(url),getJSON(url1)]);}renderAll().then(function(value){    console.log(value);})

五、Promise.race

与Promise.all相似的是,Promise.race都是以一个Promise对象组成的数组作为参数,不同的是,只要当数组中的其中一个Promise状态变成resolved或者rejected时,就可以调用.then方法了。二传递给then方法的值也会有所不同,大家可以在浏览器中运行下面的例子和上面的例子进行对比。

function renderRace() {    return Promise.race([getJSON(url),getJSON(url1)]);}renderRace().then(function(value){    console.log(value);})