回调地狱:前端的ajax和jsonp内部充斥着大量的异步,为了能够拿到异步的数据,使用了大量的回调函数,来获取将来异步执行成功之后的数据。如果请求不多,还好,一旦请求的数量达到一定程度,并且复杂度提升以后,会造成一些问题。
// 开启三个异步的程序,要求能同时拿到所有异步的结果
// 在一个异步成功之后,才能执行下一个程序,下一个程序还是异步
ajax({
url:"http://localhost/promise/data/d1.php",
success:function(res1){
console.log(res1);
ajax({
url:"http://localhost/promise/data/d2.php",
success:function(res2){
console.log(res2);
ajax({
url:"http://localhost/promise/data/d3.php",
success:function(res3){
console.log(res3);
console.log(res1, res2, res3);
}
})
}
})
}
})
// 将三个res做个拼接,以上使用了回调地域的写法
在新版本出现之后,在新的语法中,提供了一些更优雅得处理方案
- Promise(ES6)
promise,承诺,事件发生在将来,成功或失败都是在将来才会发生,但是在承诺开始得时刻,就需要提前预置,发生了成功,必然不会执行失败,发生了失败,必然不会执行成功。
语法:
Promise是一个构造函数,new的同时立即传参,参数是回调函数,回调函数身上可以接受两个参数,分别是:resolve(success,成功),reject(error,失败)
var p = new Promise(function(a,b){
// 正在执行…
// 此处放置异步的程序
// a就是在then中的第一个回调函数,表示成功要做的事情
/ b就是在catch中的第一个回调函数,表示失败要做的事情
});
p.then(function(){
// 成功的预置函数
});
p.catch(function(){
// 失败的预置函数
});
// 语法
var p = new Promise(function(a,b){
// 正在执行....
// 此处放置异步的程序
setTimeout(() => {
// a就是在then中的第一个回调函数,表示成功要做的事情
// console.log(1);
a();
}, Math.random() * 1000);
setTimeout(() => {
// b就是在catch中的第一个回调函数,表示失败要做的事情
// console.log(2);
b();
}, Math.random() * 1000);
});
var p2 = p.then(function(){
// 成功的预置函数
console.log("成功");
// 又返回了一个promise对象,那么是可以连缀执行
return p1; //假设p1是个promise对象
});
p2.then(function(){
})
p.catch(function(){
// 失败的预置函数
console.log("失败");
});
以下代码需要引入ajax.js,并且为了方便观察,将其放入一个点击事件里
// promise解决回调地域-没有保存变量版
var p1 = new Promise(function(success,error){
ajax({
url:"http://localhost/promise/data/d1.php",
success:function(res1){
// console.log(res1);
success(res1);
}
})
}).then(function(r1){
console.log(r1);
return new Promise(function(success,error){
ajax({
url:"http://localhost/promise/data/d2.php",
success:function(res2){
success(res2);
}
})
});
}).then(function(r2){
console.log(r2);
return new Promise(function(success,error){
ajax({
url:"http://localhost/promise/data/d3.php",
success:function(res3){
success(res3);
}
})
})
}).then(function(r3){
console.log(r3);
})
// promise解决回调地域-保存变量版
var p1 = new Promise(function(resolve){
ajax({
url:"http://localhost/promise/data/d1.php",
success:function(res1){
resolve(res1);
}
})
})
var p2 = new Promise(function(resolve){
ajax({
url:"http://localhost/promise/data/d2.php",
success:function(res2){
resolve(res2);
}
})
})
var p3 = new Promise(function(resolve){
ajax({
url:"http://localhost/promise/data/d3.php",
success:function(res3){
resolve(res3);
}
})
})
p1.then(function(r1){
console.log(r1);
return p2;
}).then(function(r2){
console.log(r2);
return p3;
}).then(function(r3){
console.log(r3);
})
新的需求:如何在一个位置拿到所有?使用promise得批处理
var p1 = new Promise(function(resolve){
ajax({
url:"http://localhost/promise/data/d1.php",
success:function(res1){
resolve(res1);
}
})
})
var p2 = new Promise(function(resolve){
ajax({
url:"http://localhost/promise/data/d2.php",
success:function(res2){
resolve(res2);
}
})
})
var p3 = new Promise(function(resolve){
ajax({
url:"http://localhost/promise/data/d3.php",
success:function(res3){
resolve(res3);
}
})
})
// 批处理-all:全部成功,即为成功(拿到的数据是所有数据),只要有一个失败,即为失败
// Promise.all([p1,p2,p3]).then(function(sum){
// console.log(sum);
// })
// 批处理-race:得到最先结束的状态,不管是成功(当前成功的数据)还是失败
Promise.race([p1,p2,p3]).then(function(sum){
console.log(sum);
})
ajax.js的代码:
function ajax(ops){
ops.type = ops.type || "get";
ops.data = ops.data || "";
// ops.url = ops.type=="get" ? ops.url + "?" + ops.data : ops.url;
if(ops.type=="get"){
// 在get请求时,使用时间戳避免,缓存问题
let t = new Date().getTime();
ops.url = ops.url + "?__qft="+ t + "&" + ops.data;
}
var xhr = new XMLHttpRequest();
xhr.open(ops.type, ops.url);
if(ops.type == "get"){
xhr.send();
}else{
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send(ops.data);
}
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
ops.success(xhr.responseText);
}
}
}
可将上述代码封装到ajax-promise.js文件中
// 当前ajax封装完成之后的调用方式:
// var p1 = ajax({
// type:"get",
// url:"",
// data:{
// user:"admin",
// pass:123
// }
// })
// p1.then(function(res){
// console.log(res)
// })
// p1.catch(function(res){
// console.log(res)
// })
// ajax函数自身执行的时候不再接收成功和失败的处理函数了
// 交给ajax内部封装的promise处理
// 封装过程
function ajax(ops){
ops.type = ops.type || "get";
ops.data = ops.data || {};
var str = "";
for(var key in ops.data){
str += `${key}=${ops.data[key]}&`;
}
if(ops.type=="get"){
let t = new Date().getTime();
ops.url = ops.url + "?" + str + "__qft="+ t;
}
var xhr = new XMLHttpRequest();
xhr.open(ops.type, ops.url);
if(ops.type == "get"){
xhr.send();
}else{
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded");
xhr.send(ops.data);
}
return new Promise(function(resolve,reject){
xhr.onreadystatechange = function(){
if(xhr.readyState === 4 && xhr.status === 200){
resolve(xhr.responseText);
}else if(xhr.readyState === 4 && xhr.status !== 200){
reject("当前请求失败了,失败的原因是:" + xhr.status);
}
}
})
}
- 在Promise的基础上,再使用async/await(ES7)
async function fn(){
var res1 = await new Promise(…)
var res2 = await new Promise(…)
var res3 = await new Promise(…)
var res4 = await new Promise(…)
console.log(res1,res2,res3,res4);
}
因为是在promise的基础上的语法,而ajax里面有promise,所以也可以直接调用ajax-promise.js
<script src="../ajax-promise.js"></script>
<script>
// async/await
async function fn(abc){
var p1 = await ajax({
url:"http://localhost/async-await/data/d1.php"
});
var p2 = await ajax({
url:"http://localhost/async-await/data/d12312.php"
});
var p3 = await ajax({
url:"http://localhost/async-await/data/d3.php"
});
var p3 = await ajax({
url:"http://localhost/async-await/data/d3.php"
});
var p3 = await ajax({
url:"http://localhost/async-await/data/d3.php"
});
console.log(p1, p2, p3);
console.log(abc);
}
document.onclick = function(){
fn("hello world");
}
</script>
promise是用来处理所有的异步的回调地狱,不止是ajax,任何一个异步,只要是回调地狱的调用形式,就可以使用promise改造。