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的基本使用方式
- 创建promise对象
var promise = new Promise(function(resolve,reject){
/*
在promise里进行异步操作
*/
if(异步操作的结果){
resolve(value)
}else{
reject(error)
}
})
- 通过promise对象的then,获取相应的状态 then方法里,有两个参数,这两个参数都是函数,
- 函数1 resolved的回调函数
- 函数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'))