面试官:聊聊async和await

423 阅读4分钟

前情提要

前两天面试的时候,两次面试都问到了asyncawait的作用,与使用场景。

事后我思考了下,为什么我们要用asyncawait。他们跟使用回调函数和promise处理异步,有什么优势与区别!

我将这篇文章的思路总结如下:

  1. 用回调函数处理异步
  2. 用promise处理异步
  3. 用promise + generator + co处理异步
  4. 用await 和 async 处理异步

假设一个需要使用异步的场景

1562569267434

我需要通过读取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');

虽然回调函数可以解决异步操作的问题,但是有以下缺点:

  1. 回调地狱
  2. 无法try catch
  3. 不能同步并发异步的结果

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

如果本文有任何错误的地方,还请看官们指正!