说起回调函数,在座的小伙伴们可能都用过,就是把函数 A 当作参数传递到函数 B 中,在函数 B 中以行参的方式进行调用例如如下的一个小例子
function a(cb) {
cb()
}
function b() {
console.log('我是函数 b')
}
a(b)
那么什么是回调地狱呢?言简意赅的来讲就是我在回调函数里面调用了回调函数又在调用的回调函数的回调函数里面调用了回调函数……就像下图中的代码一样
说是千层饼有点差不多,说是俄罗斯套娃也不为过,就是一个套一个,这样的话我们以后维护的成本是很高的,比如说你套了这么多层,回头别人维护你代码的时候看到这些代码怕不是要头疼的不行,很难以去理解阅读。所以说当你需要去写一个一层套一层的回调函数时,我可以给你两个解决方案,一个是去办一张健身卡以防挨打(当然如果套的太多了办健身卡也没用了,因为直接不可能维护了- -),还有一个方法就是使用Promise去解决。
在使用Promise之前,要先知道一件事情,就是他的兼容性不大行,首先IE就完全没法使(IE用户骂骂咧咧的退出了直播间),所以说既想要兼容好,又想解决回调地狱的话,Promise是不行的,可以用Jquery。
话不多说,直接上代码开整,首先我们先创建一个Promise
var p = new Promise( function( resolve , reject ){
});
其中,内部函数里的内容叫做resolver。相当于是一个承诺,里面写的就是定义承诺的规则。而参数resolve和reject就是两个状态改变工具,resolve是成功工具, reject是失败的(这两个参数的名字是可以自定义的,就是两个形参)。
比如说这一段代码我现在直接运行一下,看看当前的状态是什么
得到当前的状态是pending
当我们调用resolve时,可以将当前的状态改变为成功。调用reject的时候会将当前状态改为失败。下面我们加一个延时器,在里面我们分别去测试resolve和reject
var p = new Promise( function( resolve , reject ){
setTimeout( function(){
resolve("hello 状态改变为成功");
}, 3000 );
});
console.log(p);
<script>
var p = new Promise( function( resolve , reject ){
setTimeout( function(){
reject("hello 状态改变为失败");
}, 3000 );
});
console.log(p);
可以看到当前的状态成功被改变。那么既然目前的状态可以进行改变了,我们就要在外面写一个东西来监听他的状态,并对相关的状态进行操作。就是说成功的时候,我们要干什么,失败的时候,我们要干什么,这里我们用then和catch
p.then(function( res ){
// 如果状态成功了,那么会执行当前的函数;
console.log("状态改变为 resolved" , res);
})
p.catch( function( res ){
// 如果状态失败了,那么会执行当前的函数;
console.log("状态改变为 rejected" , res);
})
这个东西其实挺好理解的,就像是一个if语句,如果成功了就执行then函数,如果是失败的话就会执行catch函数。
下面我们来用一个例子来使用promise
function ajax( url , callback ){
var xhr = new XMLHttpRequest();
xhr.open("GET",url,true);
xhr.send();
xhr.onload = function(){
callback(xhr.responseText);
}
}
ajax( "./xxx.php" , function(){
ajax( "./xxx.php" , function(){
ajax( "./xxx.php" , function(){
})
})
})
上述代码中我们封装了一个ajax,并在调用第一个ajax的时候又调用了一个ajax,之后又调用了一个ajax,形成了一个回调地狱。接下来我们用promise去改写一下他
function ajaxPromise( url ){
var xhr = new XMLHttpRequest();
xhr.open("GET",url,true);
xhr.send();
var p = new Promise( function( resolve ){
xhr.onload = function(){
resolve(xhr.responseText);
}
})
return p;
}
var p1 = ajaxPromise( "./xxx.php " );
var p2 = p1.then( function( res ){
console.log(res,"p1");
var p2 = ajaxPromise( "./xxx.php " );
return p2;
})
var p3 = p2.then( function(res){
console.log(res,"p2")
var p3 = ajaxPromise( "./xxx.php " );
return p3;
})
p3.then(function(res){
console.log(res,"p3");
})
其中,可以看到,我们在将在 ajax 封装里面返回了一个promise对象, 在这个对象外面去监听状态的改变。这样我们就通过promise将一个一个套一个的结构改成了一个结束以后我们去通知一个状态,在别人接收到这个状态一个可以再创造一个状态发给别人,使得以前的嵌套关系变成了一个并列关系。
下面我们再去写一下promise的嵌套结构写法。
setTimeout(function(){
console.log("a");
setTimeout( function(){
console.log("b");
setTimeout( function(){
console.log("c");
setTimeout( function(){
console.log("d");
},1000)
},2000)
},1000)
},1000)
上面我们写了四个嵌套的延时器,当a打印后延时一段时间打印一个b,b打印一段时间以后打印一个c,以此类推。这是传统的写法。
接下来我们用promise去写一下
var ap = new Promise( function( resolve ){
setTimeout(function(){
console.log("a");
resolve();
},1000)
});
var ap = new Promise( function( resolve ){
setTimeout(function(){
console.log("a");
resolve();
},1000)
});
var bp = ap.then(function(){
return new Promise(function( resolve ){
setTimeout(function(){
console.log("b");
resolve();
},1000)
})
})
var cp = bp.then( function(){
return new Promise(function( resolve ){
setTimeout(function(){
console.log("c");
resolve();
},2000)
})
})
cp.then(function(){
setTimeout(function(){
console.log("d")
},1000)
})
这样我们就将一个嵌套的关系改为了一个链式关系。这就是promise的“嵌套”使用。
总结一下,promise解决回调地狱的形式就是将原来一层套一层的嵌套关系改成了一个链式关系,通俗的来讲就是不会再产生千层饼了,我们就相当于用promise这个东西吧这个千层饼里的每一层给他拆开,并列着连起来了。饼还是那个饼,味儿还是那个味,让结构更加清晰也更便于维护。