前情提要
前两天面试的时候,两次面试都问到了async
和await
的作用,与使用场景。
事后我思考了下,为什么我们要用async
和await
。他们跟使用回调函数和promise处理异步,有什么优势与区别!
我将这篇文章的思路总结如下:
- 用回调函数处理异步
- 用promise处理异步
- 用promise + generator + co处理异步
- 用await 和 async 处理异步
假设一个需要使用异步的场景
我需要通过读取number.txt得知name.txt的路径,再读取name.txt得知score.txt的路径,继而读取score.txt得知分数
1.回调函数解决异步
let fs = require("fs");
function read(filepath){
fs.readFile(filepath,'utf8',(err,data)=>{
if(!err){
fs.readFile(data,'utf8',(err,data)=>{
if(!err){
fs.readFile(data,'utf8',(err,data)=>{
console.log(data);
})
}
})
}
})
}
read('./data/number.txt');
虽然回调函数可以解决异步操作的问题,但是有以下缺点:
- 回调地狱
- 无法try catch
- 不能同步并发异步的结果
2.用promise处理异步
function read(path){
return new Promise((resolve,reject)=>{
fs.readFile(path,'utf8',(err,data)=>{
if(err){
reject(err);
}else{
resolve(data);
}
})
})
}
read('./data/number.txt').then((data)=>{
return read(data);
}).then((data)=>{
return read(data);
}).then((data)=>{
console.log(data);
})
用Promise修改过后,确实会简洁许多,然而还要多次调用then实在是有些麻烦
那么我们进一步用Promise+generator+Co来优化下
3.Promise+generator+Co处理异步
Promise
let fs = require("fs");
function readFile(path){
return new Promise((res,rej)=>{
fs.readFile(path,'utf8',(err,data)=>{
if(err){
rej(err)
}else{
res(data);
}
})
})
}
generator函数
function *read(path){
let val1 = yield readFile(path);
let val2 = yield readFile(val1);
let val3 = yield readFile(val2);
return val3;
}
Co函数
function Co(oIt){
return new Promise((res,rej)=>{
let next = (data)=>{
//generator每次next都会返回一个对象{value:,done:,}
//这里的value就是一个promise对象,
let {value,done}=oIt.next(data);
if(done){
//只要done不是true,就证明生成器函数还没结束
res(value);
}else{
value.then((val)=>{
//value是一个promise对象,
//所以此处可以将val这个结果传给next
//递归去处理
next(val);
})
}
}
next();
})
}
结果:
let readNum = read('./data/number.txt');
Co(readNum).then((val)=>{
console.log(val);
})
这样修改后,确实比较简洁了,但麻烦的地方是,要么我们自己写个Co函数,要么去引入一个Co的npm包,有没有更好的方法呢
接下来讲用await和async优化
4.await 和 async处理异步
//async 表示后面的函数里有异步操作
//并且该函数,最终会返回一个promise对象
async function read(path){
//await 会返回 后面promise对象的处理结果
let val1 = await readFile(path);
let val2 = await readFile(val1);
let val3 = await readFile(val2);
return val3;//即使这里的val3是个string类型
//返回出去时,也会被包装成一个promise对象
}
read('./data/number.txt').then((val)=>{
console.log(val);
})
用await和async后,就不用再去写co函数了,使用起来也更加容易理解些
如果你用babel去转译下,会发现await和async底层也是用promise+generator+co实现的
1.await和async可以使用 try catch
异步的回调函数是无法用try catch捕捉错误的
let fs = require("fs");
function read (){
fs.readFile('./data/number.txt','utf8',(err,data)=>{
throw Error("出错了");
if(err){
console.log(err);
}else{
console.log(data);
}
})
}
try{
read();
}catch(e){
console.log("这是错误信息",e);
}
执行后会发现,输出的错误信息中,没有“这是错误信息”这个字段,证明回调函数是无法用try catch的
再来看await和asnyc
async function read(url){
try{
let val1 = await readFile(url);
let val2 = await readFile(val1);
let val3 = await readFile(val2);
return val3
}catch(e){
console.log("这是错误信息",e);
}
}
let readNum = read('./data/number.txt1');
readNum.then((val)=>{
console.log(val);
})
执行后可以看到错误信息中有“这是错误信息”的字段, 证明用async和await的异步处理,错误信息可以被捕捉。
2. async和await处理同步并发异步的结果
一般来说我们处理同步并发有Promise.all这个方法
Promise.all([readFile('./data/1.txt'),readFile('./data/2.txt'),readFile('./data/3.txt')]).then((val)=>{
console.log(val);
},(err)=>{
console.log(err);
})
但如果其中一个异步请求出了错误,我们要找到该错误,promise.all就不能实现了
这时可以有用await和async来实现
async function read1(){
let val1 = null;
try{
val1 = await readFile('./data/1.txt');
console.log(val1);
}catch(e){
console.log("这是错误信息",e);
}
}
read2,read3同上
function readAll(...args){
args.forEach((ele)=>{
ele();
})
}
这时只要其中一个异步出现了错误,我们就可以找到了错误的地方了。
结语
本文作者:胡志武
时间:2019/7/8
如果本文有任何错误的地方,还请看官们指正!