JavaScript---Promise

  什么是Promise?

     Promise是ES6新增的对象。他是构造函数,通过构造实例来使用他的方法。 var p = new Promise();

 

   Promise是干什么用的

    用来传递异步操作的消息(如ajax请求),它代表了某个未来才会知道结果的事件(通常是一个异步操作)

 

   Promise的3种状态:

       1.Pending(未完成)---译为‘即将发生的’,可以理解为Promise对象创建实例的初始状态;

        2. Resolved(成功)---译为‘已解决的’,可以理解为成功的状态;

        3.Rejected(失败)--译为‘拒绝的’,可以理解为失败的状态。

 

   Promise对象的特点

        1.Promise的状态不受外界影响。只有异步操作的结果,才可以决定当前是什么状态,其他任何操作都无法改变他的状态。

        2.一旦状态发生改变,就不会再变,任何时候都可以得到这个结果。Promise对象的的状态改变,只有两种情况:1.Pending变为Resolved, 2.Pending变为Rejected。只要这两种情况发生其中一个,另一个是不会发生的。因为状态会凝固,即使你还想添加回调函数试图改变结果,也不起作用。

    什么时候用promise?函数回调多层嵌套(有依赖关系)

      来看一个demo:多个ajax的请求(存在依赖)

 

      常用的方式:使用函数嵌套

 

$.ajax({
                url:'http://localhost/api/req1.php',
                success:function(str1){
                    console.log(str1);

                    $.ajax({
                        url:'http://localhost/api/req2.php',
                        success:function(str2){
                            console.log(str2);

                            $.ajax({
                                url:'http://localhost/api/req3.php',
                                success:function(str3){
                                    console.log(str3);

                                    document.body.innerHTML = str1 + str2 + str3
                                }
                            })
                        }
                    })
                }
            })

 

 

 

  ajax的多层嵌套会造成阻塞,只有前一个请求成功了,下一个请求才会开始执行,万一前一个请求执行失败,下一个请求永远无法执行,造成页面‘假死’。

  再来看promise是如何处理的?

    Promise 构造函数接受一个函数作为参数,该函数的两个参数分别是 resolve 方法和 reject 方法。

    如果异步操作成功,则用 resolve 方法将 Promise 对象的状态,从「未完成」变为「成功」(即从 pending 变为 resolved)。

    如果异步操作失败,则用 reject 方法将 Promise 对象的状态,从「未完成」变为「失败」(即从 pending 变为 rejected)。

 

 

var p1 = new Promise(function(resolve,reject){
            // 这时的状态:pending
            $.ajax({
                url:'http://localhost/api/req1.php',
                success:function(str1){
                    // console.log(str1);
                    
                    
                    //把promise对象的状态改成resolved
                    //只要状态被修改成resolved,就不会再改变
                    resolve(str1);
                }
            })
        });

        var p2 = new Promise(function(resolve,reject){
            $.ajax({
                url:'http://localhost/api/req2.php',
                success:function(str2){
                    // console.log(str2);

                    resolve(str2);
                }
            })
        });

        var p3 = new Promise(function(resolve,reject){
            $.ajax({
                url:'http://localhost/api/req3.php',
                success:function(str3){
                    // console.log(str3);

                    resolve(str3)
                }
            });
        });


        // 等3个请求都成功后
        // 把数据写入页面
        var pAll = Promise.all([p1,p2,p3]);

        pAll.then(function(data){
            console.log('所有:',data);
        });


        // 返回第一个完成的数据
        var pFast = Promise.race([p1,p2,p3]);

        pFast.then(function(data){
            console.log('手速最快:',data);
        });

 

    再来看具体的实例:

      比如:获取当前城市的天气预报API-->获取当前城市API-->获取当前城市的ip地址API

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>多个ajax请求</title>
    <script src="js/jquery-3.1.1.js"></script>
    <script>
    $(function(){
        /*
            * http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=json&ip=58.248.240.46

            * http://ip.taobao.com/service/getIpInfo.php?ip=58.248.240.46

            
            ajax依赖数据解决方案
            * ajax嵌套
                * 层级太深,难以维护
            * 同步请求
                * 影响其他代码的执行
            * ES6 Promise
                *     
         */
        
        var currentCity;


        // 通过IP获取当前城市
        var cityPromise = new Promise(function(resolve,reject){
            $.ajax({
                url:'../getCity.php',
                // async:false,
                dataType:'json',
                success:function(data){
                    console.log(data);
                    currentCity = data.data.city.replace(/市$/,'');
                    console.log(currentCity);

                    resolve(currentCity);
                },
                error:function(){
                    reject('请求失败')
                }
            });
        });

        cityPromise.then(function(city){
            console.log('成功',city);

            $.ajax({
                url:'http://wthrcdn.etouch.cn/weather_mini',
                dataType:'json',
                data:{city:currentCity},
                success:function(data){
                    console.log(data)
                }
            });
        },
        function(){
            console.log('失败')
        });


        
    })
    </script>
</head>
<body>
    
</body>
</html>

 

  在chrome中显示:

 

  补充:Promise对象的内置方法

    Promise.all(p1,p2,p3...)---将多个Promire 实例包装成一个新的Promise实例 注:所有的参数中的promise状态都为resolved时,新的promise状态才为

resolved,一个是rejected,新的promise状态为rejected。

    Promise.race()---竞速模式  返回最先执行完的resolved结果。

    原型方法:Promise.prototype.then(successFn[,failFn]) ---Promise实例生成以后,可以用then方法分别指定Resolved状态和Rejected状态的回调函数。并根据Promise对象的状态来确定执行的操作:

        1.resolved时执行第一个函数successFn。

        2.rejected时执行第二个函数failFn。

var p = new Promise(function(resolve, reject){
    //ajax请求
    ajax({
        url:'xxx.php',
        success:function(data){
            resolve(data)
        },
        fail:function(){
            reject('请求失败')
        }
    });
});

//指定Resolved状态和Rejected状态的回调函数
//一般用于处理数据
p.then(function(res){
    //这里得到resolve传过来的数据
},function(err){
    //这里得到reject传过来的数据
})

      Promise对象就暂告一段落, 细节以后慢慢补充         

                                       2017-3-27   15:20

---------------------------------------------------------------------------------更新!!!!----------------------------------------------------------------------------

前天面试的时候,面试官有问到promise用法,但是好久没用了。竟然忘了!!!!! 这充分说明了,回顾知识是多么重要!!! 你永远不知道,你下一秒会不会用到。

  回到正题:

    前面说道,创建一个promise对象,只需“new”一个就好了。

 var p1 = new Promise(function(resolve,reject){
            setTimeout(function () {
                alert('666');
            },2000);
        })

  试着执行这一段代码,2秒后弹出框“666”。 感觉有点怪怪的,我只是“new”出来一个promse对象而已,并没有调用啊,他怎么自动执行了?注意这个细节。一般地,promise对象放到一个函数里面,不要让他自动执行。

  

function runAsync() {
            var p1 = new Promise(function (resolve, reject) {
          // 异步操作的代码 setTimeout(
function () { alert('666'); }, 2000); }); return p1 } runAsync();

 

  上面的写法,返回实例promise。然后直接可以调用then() 方法了。 then方法接收一个函数作为参数,并且会拿到我们在runAsync中调用resolve时传的的参数。

来,感受一下:

  

 function runAsync() {
            var p1 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                   console.log('666');
                    var obj = {
                        name:'jjk',
                        age:18
                    }
                    resolve(obj)
                }, 2000);
            });
            return p1
        }
        runAsync().then(function(data){
            console.log(data);
        })

  在chrome中显示:

  

 then里面的函数就跟我们平时的回调函数一个意思,能够在runAsync这个异步任务执行完成之后被执行。这就是Promise的作用了,简单来讲,就是能把原来的回调写法分离出来,在异步操作执行完后,用链式调用的方式执行回调函数。看到这里,疑问就来了,回调函数不是也可以做到这一点么?

function runAsync(callback){
             setTimeout(function () {
                    console.log('666');
                    var obj = {
                        name:'jjk',
                        age:18
                    }
                   callback(obj);
                }, 2000);
        }
        runAsync(function(data){
            console.log(data);
        })

  看来Promise不怎么样啊。别急,试想一下,有多层回调该怎么办?如果callback也是一个异步操作,而且执行完后也需要有相应的回调函数,该怎么办呢?总不能再定义一个callback2,然后给callback传进去吧。而Promise的优势在于,可以在then方法中继续写Promise对象并返回,然后继续调用then来进行回调操作。从表面上看,Promise只是能够简化层层回调的写法,而实质上,Promise的精髓是“状态”,用维护状态、传递状态的方式来使得回调函数能够及时调用,它比传递callback函数要简单、灵活的多。所以使用Promise的正确场景是这样的:

function runAsync1() {
            var p1 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log('111');

                    resolve('第一次')
                }, 2000);
            });
            return p1
        };
        function runAsync2() {
            var p2 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log('222');
                    resolve("第二次")
                }, 2000);
            });
            return p2
        };
        function runAsync3() {
            var p3 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log('333');
                    resolve("第三次")
                }, 2000);
            });
            return p3
        };

        runAsync1().then(function (data) {
            console.log(data);
            return runAsync2()
        })
        .then(function (data) {
                console.log(data);
                return runAsync3()
            })
        .then(function (data) {
                console.log(data);
            })

  在chrome中显示:

    

  在then方法里面直接返回promise实例,然后继续链式调用。那么,如果我不返回实例,而是直接返回数据,还能链式调用吗?

  

 function runAsync1() {
            var p1 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log('111');

                    resolve('第一次')
                }, 2000);
            });
            return p1;
        };
        function runAsync2() {
            var p2 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log('222');
                    resolve("第二次")
                }, 2000);
            });
            return p2;
        };
        function runAsync3() {
            var p3 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    console.log('333');
                    resolve("第三次")
                }, 2000);
            });
            return p3;
        };

        runAsync1().then(function (data) {
            console.log(data);
            return '直接返回数据看看'
        })
        .then(function (data) {
                console.log(data);
                return runAsync3()
            })
        .then(function (data) {
                console.log(data);
            })

  在chrome中显示

结果显而易见,返回数据之后,还可以调用then方法,可then方法不是promise的方法吗? 答案是这样的:then方法返回数据时,会把数据包装成promise对象,以供下一个then调用。

 

  事实上,我们前面的例子都是只有“执行成功”的回调,还没有“失败”的情况,reject的作用就是把Promise的状态置为rejected,这样我们在then中就能捕捉到,然后执行“失败”情况的回调。

  

function runAsync() {
            var p1 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    var num = Math.ceil(Math.random() * 10);

                    if (num <= 5) {
                        resolve(num);
                    } else {
                        reject('warming')
                    }
                }, 1000)
            });
            return p1;
        }
        runAsync().then(function (data) {
            console.log('resolved');
            console.log(data);
        }, function (reason, data) {
            console.log('rejected');
            console.log(reason);
            console.log(data);
        })

在chrome中显示:

   或者

 可以看出then方法接收2个函数作为参数,第一个参数是状态成功时执行,第二个参数是状态失败后执行,该函数的第一个参数就是reject的数据。

 

  说完了then方法,还要说说其他方法

  catch,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样:

function runAsync() {
            var p1 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    var num = Math.ceil(Math.random() * 10);

                    if (num <= 5) {
                        resolve(num);
                    } else {
                        reject('warming')
                    }
                }, 1000)
            });
            return p1;
        }
        runAsync()
        .then(function (data) {
            console.log('resolved');
            console.log(data);
        })
        .catch(function(err){
            console.log('rejected');
            console.log(err);
        })

  不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。来看代码:

function runAsync() {
            var p1 = new Promise(function (resolve, reject) {
                setTimeout(function () {
                    var num = Math.ceil(Math.random() * 10);

                    if (num <= 5) {
                        resolve(num);
                    } else {
                        reject('warming')
                    }
                }, 1000)
            });
            return p1;
        }
        runAsync()
        .then(function (data) {
            console.log('resolved');
            console.log(data);
            console.log(num);
        })
        .catch(function(err){
            console.log('rejected');
            console.log(err);
        })

   当进入resolve状态时,在chrome中显示:

  

  这个跟try/catch 类似。

 

posted @ 2017-05-19 17:35  游鱼与渔  阅读(358)  评论(0编辑  收藏  举报