一.认识async/await
async/await其实是Generator的语法糖,它能实现的效果都能用then链来实现,它是为优化then链而开发出来的。从字面上来看,async是“异步”的简写,await是等待,所以很好理解async是用于声明异步的,而await用于等待一个异步方法执行完成。当然语法上强制规定await只能出现在asnyc函数中。
1.async
- async函数返回的是一个Promise对象。async函数乳瓜在函数中return一个直接量,async会把这个直接量通过Promise.resolve() (Promise.resolve()可以看做是new Promise(resolve => resolve()的简写)封装成Promise对象。
async function test(){
return 'hello world'
}
let res = test()
console.log(res)
- async函数返回的是一个Promise对象,所以在最外层不能用await获取其返回值的情况下,用原来的方式:then()来处理这个Promise对象:
async function test(){
return 'hello world'
}
let res = test()
console.log(res) //Promise对象
res.then(result => {
console.log(result) //hello world
})
- 联想一下Promise的特点——无等待,所以在没有await的情况下执行async函数,它会立即执行,返回一个Promise对象,并且,绝不会阻塞后面的语句。
async function test(){
return 'hello world'
}
let res = test()
console.log(res)
res.then(result => {
console.log(result)
})
console.log("aaa") //立即执行aaa
2.await
一般来说,都认为await是在等待一个async函数完成。不过按语法说明,await等待的是一个表达式,这个表达式的计算结果是Promise对象或者其他值。因为async函数返回一个Promise对象,所以await可以用于等待一个async函数的返回值——也可以说是await是在等async函数,但要清楚,它等的实际是一个返回值。注意到await不仅仅用于等Promise对象,它可以等任意表达式的结果,所以await后面实际是可以接普通函数调用或者直接量的。
- await表达式的结果取决于它等的是什么:
- 如果等到的不是一个Promise对象,那么await表达式的运算结果就是它等到的东西
- 如果等到的是一个Promise对象,那么await就忙起来了,它会阻塞后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果。
function test1(){
return "test1"
}
async function test2(){
return "test2"
// return Promise.resolve("test2")
}
async function test3(){
const res1 = await test1() //不是Promise对象
const res2 = await test2() //Promise对象
console.log(res1,res2) //test1 test2
}
test3()
- await必须用在async函数中。async函数调用不会造成阻塞,它内部所有的阻塞都被封装在一个Promise对象中异步执行。await会暂停当前async的执行。
function test1(x){
return new Promise(resolve => {
setTimeout(() =>{
resolve(x)
},3000)
})
}
async function test2(){
let res = await test1('hello world')
console.log(res) //3s后输出hello world
console.log("aaa") //3s后输出aaa
}
test2()
console.log("bbb") //立即输出bbb
二.async/await如何捕获异常
- 如果await后面的异步操作出错,那么等同于async函数返回的Promise对象被reject。
async function test(){
return Promise.reject('错误')
}
test().then(res => {
console.log("success:",res)
},err => {
console.log("error:",err)
})
//error:错误
- 防止出错的方法,是将其放在try…catch代码块中。
async function test(){
try{
await new Promise(function(resolve,reject){
throw new Error('错误')
});
} catch(err){
console.log("error:",err)
}
return await('成功');
}
三.async/await优势
单一的 Promise 链并不能发现 async/await 的优势,但是,如果需要处理由多个 Promise 组成的 then 链的时候,优势就能体现出来了(很有意思,Promise 通过 then 链来解决多层回调的问题,现在又用 async/await 来进一步优化它)。
假设一个业务,分多个步骤完成,每个步骤都是异步的,而且依赖于上一个步骤的结果。仍然用 setTimeout 来模拟异步操作:
/**
* 传入参数 n,表示这个函数执行的时间(毫秒)
* 执行的结果是 n + 200,这个值将用于下一步骤
*/
function takeLongTime(n) {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200), n);
});
}
function step1(n) {
console.log(`step1 with ${n}`);
return takeLongTime(n);
}
function step2(n) {
console.log(`step2 with ${n}`);
return takeLongTime(n);
}
function step3(n) {
console.log(`step3 with ${n}`);
return takeLongTime(n);
}
现在用 Promise 方式来实现这三个步骤的处理:
function doIt() {
console.time("doIt");
const time1 = 300;
step1(time1)
.then(time2 => step2(time2))
.then(time3 => step3(time3))
.then(result => {
console.log(`result is ${result}`);
console.timeEnd("doIt");
});
}
doIt();
// c:\var\test>node --harmony_async_await .
// step1 with 300
// step2 with 500
// step3 with 700
// result is 900
// doIt: 1507.251ms
输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果一致。
如果用 async/await 来实现呢,会是这样:
async function doIt() {
console.time("doIt");
const time1 = 300;
const time2 = await step1(time1);
const time3 = await step2(time2);
const result = await step3(time3);
console.log(`result is ${result}`);
console.timeEnd("doIt");
}
doIt();
结果和之前的 Promise 实现是一样的,但是这个代码看起来是不是清晰得多,几乎跟同步代码一样
四.async/await对比Promise的优势
- 代码读起来更加同步,Promise虽然摆脱了回调地狱,但是then的链式调用也会带来额外的阅读负担。
- Promise传递中间值非常麻烦,而async/await几乎是同步的写法,非常优雅。
- 错误处理友好,async/await可以用成熟的try/catch,Promise的错误捕获非常冗余。。
- 调试友好,Promise的调试很差,由于没有代码块,你不能在一个返回表达式的箭头函数中设置断点,如果你在一个.then代码块中使用调试器的步进功能,调试器并不会进入后续的.then代码块,因为调试器只能跟踪同步代码的每一步。