要想了解JS中的回调地狱,首先要从两个概念说起:
一.回调函数:
当一个函数作为参数传入另一个函数中,并且它不会立即执行,只有在满足一定条件时,才可以执行,这种函数就称为回调函数。
setTimeout(function(){
console.log("执行了回调函数");
},3000)
在上述代码中,function就是一个回调函数,它作为一个参数传递到定时器函数中,只有在满足延时3秒的条件后,才会执行该回调函数。
二.异步任务:
我们都了解js是异步的。任务类型分为同步任务和异步任务:同步任务在主线程上排队执行,只有当前一个任务执行完毕之后,才能执行下一个任务;而同步任务不进入主线程,而是进入异步任务队列,前一个任务是否执行完毕不影响下一个任务的执行。
setTimeout(function(){
console.log("执行了回调函数");
},3000)
console.log("输出111,执行异步任务");
从上述的代码示例可以看出,定时器中的任务是否执行完毕不影响后面 任务的执行,当定时器需要等待3秒再去执行时,不会阻塞后面函数的执行。这就是简单的异步任务的实现。
三.JS中回调地狱是什么:
根据上述对回调函数和异步任务的介绍我们可以得出一个结论。存在异步任务的代码,不能保证代码能够按照顺序执行,但是如果我们必须要让代码按照顺序执行呢?
假如要说一句话,语序是这样的:早上起床先睁开眼睛,再去洗漱,最后穿衣服。
如果我们要让上面的话按照语序输出,就需要这样操作:
setTimeout(function(){
console.log("早上起床先睁开眼睛");
setTimeout(function(){
console.log("再去洗漱");
setTimeout(function(){
console.log("最后穿衣服");
},1000)
},2000)
},3000)
从上面的实例可以看出,如果要按照语序输出一句话,就需要回调函数嵌套回调函数,上面代码就嵌套了三层回调函数,这种回调函数中嵌套回调函数的情况就称为回调地狱。
回调地狱是为了实现代码执行操作顺序而出现的一种操作,回调地狱会造成带代码的可读性非常差并且不利于后期代码的维护。
四.解决回调地狱的方法:
4.1使用Promise
promise是js原生的一个对象,是解决异步编程的一种方案。
1.promise构造函数接收一个函数作为参数,在该函数体内就可以写我们要执行的异步任务,p该函数的两个参数是resolve和reject。当异步任务成功执行时调用resolve函数返回结果,如果失败就调用reject函数。
2.promise对象中的then方法用来接收处理成功时候的相应数据,catch方法用来接收处理失败时的相应数据。
3.promise的链式编程可以保证代码的执行顺序,但是前提是每一次在执行完then中的操作时,必须要返回一个promise对象,这样才能保证下一次的then时能够接收到数据.
function fn(str){
var p=new Promise(function(resolve,reject){
var flag=true;
setTimeout(function(){
if(flag){
resolve(str)
}
else{
reject('失败')
}
})
})
return p;
}
fn('早上起床先睁开眼睛').then((data)=>{
console.log(data);
return fn('再去洗漱')
})
.then((data)=>{
console.log(data);
return fn('最后穿上衣服')
})
.then((data)=>{
console.log(data);
})
.catch((data)=>{
console.log(data);
})
这样可以实现使用promise对象来实现按照代码顺序执行任务 ,运行结果如下:
但是我们通过对代码的观察可以看出,使用promise的问题就是代码冗余,使用promise封装异步任务就会出现很多then,这样也是不利于代码维护。
4.2使用async/await
async作为关键字放到一个声明函数的前面,声明该函数是一个异步任务,不会阻塞后面任务的执行
async function fn(){
console.log('张峻豪最帅');
}
console.log(fn());
我们可以看出使用async关键字将函数自动封装成了一个promise对象,所以就可以在使用async中的函数中使用promise的方法。
await关键字只能在使用async声明的函数中使用,await后面可以直接跟一个promise实例对象,可以直接拿到promise中resolve返回的数据。
//封装一个返回promise的异步任务
function fn(str){
var p=new Promise(function(resolve,reject){
var flag=true;
setTimeout(function(){
if(flag){
resolve(str)
}
else{
reject('处理失败')
}
},1000)
})
return p;
}
async function test(){
var res1=await fn('早上起床先睁开眼睛');
var res2=await fn('再去洗漱');
var res3=await fn('最后穿上衣服');
console.log(res1,res2,res3);
}
test();
上述代码执行结果如下:
async和await实际上是ES7提供给我们的语法糖,这样就解决了使用原生promise解决回调地狱中遇到的代码可读性不高,代码冗余和代码不利于维护的问题,使一个异步代码看起来更像是一个同步代码。