AJAX 异步

在上一个章节中,我们学习了使用ajax进行网络请求.ajax的好处是通过异步请求对页面内容进行无重载刷新.但也同样是因为这个 异步 特性,给我们的代码编写和维护带来麻烦.

回调地狱

所谓回调地狱,就是在回调函数嵌套里其他回调函数,当层次越来越深,代码量越来越大的情况,程序就变得难以维护.

例如下面的动画

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=`, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .ball {
            position: absolute;
            top:10px;

            width: 50px;
            height: 50px;
            border-radius: 50%;
        }
        #ball1 {
            background: red;
        }
        #ball2 {
            background: cyan;
            left: 60px;
        }
    </style>
</head>
<body>
    <div id="ball1" class="ball"></div>
    <div id="ball2" class="ball"></div>
</body>
<script>
    var ball1 = document.getElementById("ball1");
    var ball2 = document.getElementById("ball2");
    console.log([ball1]);
    function animate(ball,top,callBack){
        setTimeout(function(){
            var topDis = parseInt(ball.offsetTop,10);
            if(topDis === top){
                callBack && callBack();
            }else{
                if(topDis < top){
                    topDis += 2;
                }else{
                    topDis -=2;
                }
                ball.style.top = topDis+"px";
                animate(ball,top,callBack)
            }
        },13);
    }

    animate(ball1,100,function(){
        animate(ball2,200,function(){
            animate(ball1,200,function(){
                console.log("动画结束");
            })
        })
    })
</script>
</html>

从上面的代码中,我们可以看到完成这个简单的动画,我们就需要嵌套三层回调函数,如果动画再多几次移动,则会有更多层的回调等待执行.例如实现下图的动画

animate(ball1,100,function(){
    animate(ball2,200,function(){
        animate(ball1,200,function(){
            animate(ball1,100,function(){
                console.log("动画结束");
                animate(ball2,250,function(){
                    console.log("动画结束");
                })
            })
        })
    })
})

看了上面的代码,是不是有一种莫名的恐惧感?它就像盗梦空间一样,梦境一个嵌套另一个,然后你懵了.我是谁,我在哪里.我不写了!

promise

为了解决回调地狱的问题,在ES6里,提供了解决方案,让我们可以以同步的方式,实现异步代码.可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果.

Promise的三种状态

  • pending(进行中)
  • fulfilled(已成功)
  • rejected(已失败)

这三种状态之间的变化关系

  • pending (进行中) -> fulfilled(已成功)
  • pending(进行中)-> rejected(已失败)

当Promise的状态确定之后,就不会再放生变化.因此我们也叫它"承诺编程"

Promise的基本使用方式

  1. 创建promise对象

var promise = new Promise(function(resolve,reject){
 /*
     在promise里进行异步操作
 */ 
 if(异步操作的结果){
     resolve(value)
 }else{
     reject(error)
 }
})
  1. 通过promise对象的then,获取相应的状态 then方法里,有两个参数,这两个参数都是函数,
  2. 函数1 resolved的回调函数
  3. 函数2 rejected状态的回5调函数
promise.then(function(resp){

},function(error){

})

then的链式调用

promise.then执行完成之后,得到一个新的promise对象,而该promise对象也有then方法.

var promise2 = promise.then(function(resp){
    return;
},function(error){

})

promise2.then(function(resp){

},function(error){

})

以上代码可以通过链式方式简化

promise.then(function(resp){
    return;
},function(error){

}).then(function(resp){

},function(error){

})

使用promise控制动画

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=`, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <style>
        .ball {
            position: absolute;
            top:10px;

            width: 50px;
            height: 50px;
            border-radius: 50%;
        }
        #ball1 {
            background: red;
        }
        #ball2 {
            background: cyan;
            left: 60px;
        }
    </style>
</head>
<body>
    <div id="ball1" class="ball"></div>
    <div id="ball2" class="ball"></div>
</body>
<script>
    var ball1 = document.getElementById("ball1");
    var ball2 = document.getElementById("ball2");

    function animatePromise(ball,top){
      return  new Promise((resovle)=>{
        _animate();
        function _animate(){
          setTimeout(function(){
            var topDis = parseInt(ball.offsetTop,10);
            if(topDis === top){
                resovle();
            }else{
                if(topDis < top){
                    topDis += 2;
                }else{
                    topDis -=2;
                }
                ball.style.top = topDis+"px";
                _animate(ball,top)
            }
           },13);
        }
      })
    }
    animatePromise(ball1,100)
    .then(()=>{
      return animatePromise(ball2,200)
    })
    .then(()=>{
      return animatePromise(ball1,300)
    })
</script>
</html>

使用Promise对ajax请求进行封装

我们在上一章封装的ajax请求的基础上,再进行二次封装 代码下载

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
   <script src="../bower_components/mockjs/dist/mock.js"></script>
  <script src="../bower_components/axios/dist/axios.js"></script>
  <script src="../bower_components/vue/dist/vue.js"></script>
  <script>
    const url = 'http://localhost:9999/table';
    const rs = Mock.mock(url, {
      'dataSource|10': [{
        'key|+1': 1,
        'name': '@name'
      }]
    })
    console.log(rs);
  </script>

</head>
<body>
  <button type="button" id="loadBTN">加载</button>
</body>
</html>
<script>
const obj = {
        ajax: function(options){
          let _this = this;
          return new Promise(function(resolve,reject){
            var method = options.method?options.method.toLowerCase():"get";
            var async = options.async?options.async:true;
            var url = options.url?options.url:"";
            var data = options.data?options.data:{};
            var dataType = options.dataType?options.dataType:"json";
            var jsonpCallback = options.jsonpCallback?options.jsonpCallback:"callback";
            var jsonp = options.jsonp?options.jsonp:"callback";
            var success = options.success?options.success:function(data){}
            var error = options.error?options.error:function(){}
            var complete = options.complete?options.complete:function(){}
            var headers = options.header?options.header:{
                "Content-type":"application/x-www-form-urlencoded"
            }
             //判断是否JSOP,
            if(dataType==="jsonp"){
                console.log("jsonp请求");
                //动态生成script标签
                _this.dynamicScript(url+"?"+jsonp+"="+jsonpCallback);
               //将回调函数注册到全局对象window中
                window[jsonpCallback] = function(data){
                    console.log("请求的结果",data);
                    if(data!=null){
                        success(data);
                        resolve(data);
                    }else {
                        error("请求的结果为空",data)
                        reject(data);
                    }
                    complete(data);
                }
                return;
            }
              //如果是get请求,判断data是字符串还是对象,如果是对象则序列化参数
              if(method=="get"){
                if((typeof data) =="string"){
                    url += "&"+data;
                }
                if(typeof data == "object"){
                    url += "&"+_this.format(data)
                }
                url = url.slice(0,-1)
            }
            console.log(">>>>>",url,method,async);
            var xhr = new XMLHttpRequest();
            xhr.open(method,url,async);
            //设置请求头
            for(var key in headers){
                xhr.setRequestHeader(key,headers[key])
            }
           //处理post请求的数据
           var dataStr = JSON.stringify(data);
           xhr.send(dataStr);
           xhr.onreadystatechange = function(){
                var status = {
                   "204":"服务器没有返回内容",
                   "401": "验证失败",
                   "403": "没有权限访问该资源",
                   "404": "请求的资源不存在",
                   "500": "服务器出错",
                   "503": "服务器超负荷,无法完成请求"
               }
               var result = null;
               //判断ajax请求是否完成
               if(xhr.readyState===4){
                   //判断服务器返回的请求状态是否为200
                   if(xhr.status===200){
                       console.log(xhr.responseText);
                       //获取服务器返回的数据
                        var response = xhr.responseText;
                        //如果dataType为json,则需要将数据转换成json格式
                        if(dataType=="json"){
                            result = JSON.parse(response);
                        }else {
                            //其他格式原样输出
                            result = response;
                        }
                        success(result);
                        resolve(result);
                   }else{
                       error(status[xhr.status])
                       reject(status[xhr.status]);
                   }

               }
           }
          })

        },
        format: function(obj){
            var rs = ""
            for(var key in obj){
                rs += key+"="+obj[key]+"&"
            }
            return rs.slice(0,-1);
        },
        dynamicScript: function(src){
            var script_dom = document.createElement('script');
            script_dom.src = src;
            script_dom.language = 'javascript';
            script_dom.type = 'text/javascript';
            var head = document.getElementsByTagName('head').item(0);
            head.appendChild(script_dom);
        }
    }
const axios = {
    get:function(options){
      return obj.ajax(options);
    },
    post:function(options){
      options.method = 'post';
      return obj.ajax(options);
    }
}

let loadBTN = document.getElementById('loadBTN');
loadBTN.onclick = function(){
    console.log("点击",url);
    axios.get({url:url}).then(function(resp){
        console.log("请求结果》》》》",resp);
    });
}

</script>

catch()

当我们在使用promise的过程中,出现错误,例如内容未定义.我们就可以通过catach对该错误进行捕捉,它也可以捕捉rejected状态.

axios.get(url,params)
.then(function(resp){
    throw new Error('test');
}).catch(function(exception){

})

finally()

finally方法用于指定不管 Promise 对象最后状态如何,都会执行的操作.

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

Promise.all()

用于将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.all([p1, p2, p3]);
  • p的状态由p1、p2、p3决定,分成两种情况。

  • 只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

  • 只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

Promise.race()

Promise.race方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例。

const p = Promise.race([p1, p2, p3]);

上面代码中,只要p1、p2、p3之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的 Promise 实例的返回值,就传递给p的回调函数。

Promise.resolve()

有时需要将现有对象转为 Promise 对象,Promise.resolve方法就起到这个作用。

const jsPromise = Promise.resolve($.ajax('/whatever.json'))

results matching ""

    No results matching ""