经过前面的学习我们已经了解了目前JS中常用的异步编程方式,如Promise、Generator、async/await,本文将通过以上方式来解决回调地狱问题。
回顾一下异步编程的方式,早些年为了实现JS的异步编程,一般都采用回调函数的方式,比较典型的是事件的回调,但是使用回调函数来实现存在一个很常见的问题,那就是回调地狱。
1. 什么是回调地狱
异步任务中回调函数的层层嵌套,随着嵌套的层数增加,代码的可读性和维护性变差,常见的回调函数比如在setTimeout中,以函数作为参数进行调用。
fs.redFile(A,'UTF-8',function(err,data){// 第一层
fs.redFile(B,'UTF-8',function(err,data){// 第二层
fs.redFile(C,'UTF-8',function(err,data){// 第三层
fs.redFile(D,'UTF-8',function(err,data){// ...
...
})
})
})
})
利用回调函数实现异步编程的场景有很多,如下:
- ajax请求回调
- 定时器中的回调
- 事件回调
- Nodejs中的回调
接下来,讨论如何解决回调地狱的问题
2. 回调地狱的解决
2.1.1 promise初级版本
function read(url){
return new Promise((resolve, reject) => {
fs.readFil(url, 'UTF-8',(err, data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
read(A).then(data=>{
return read(B)
}).then(data=>{
return read(C)
}).then(data=>{
return read(D)
}).catch(reason=>{
console.log(reason);
})
这样写的方法相比于回调函数的可读性是增加了,接下来用Promise的API,all方法进行实现
2.1.2 Promise进阶版本
function read(url){
return new Promise((resolve,reject)=>{
// 待执行的异步函数
fs.readFil(url,'UTF-8',(err,data)=>{
if(err){
reject(err)
}else{
resolve(data)
}
})
})
}
Promise.all([read(A),read(B),read(C),read(D)]).then(data=>{
console.log(data);
}).catch(err=>{
console.log(err);
})
相比于上面的方式,用all方法看起来更加地简洁。
2.2 Generator
为什么第一次传递的参数无效呢?
function * gen(){
let a = yield 111;
console.log(a);
let b = yield 222;
console.log(b);
let c = yield 333;
console.log(c);
}
let t = gen()
console.log(t.next(1)); // 第一次传递参数无效,无打印结果
console.log(t.next(2));
console.log(t.next(3));
console.log(t.next(4));
2.3 async/await
代码清晰,异步代码看起来像同步一样。
function teastWait(){
return new Promise((resolve, reject)=>{
setTimeout(function(){
console.log("testWait");
resolve()
}, 1000);
})
}
async function testAwaitUse(){
await teastWait()
console.log("hello");
return 123
}
console.log(testAwaitUse());
// 猜测打印顺序
// hello,testWait无await情况下;存在则先打印testwait,hello
3. 总结