JS | async和await
async和await的定义
任何一个名称都是有含义的,先从字面意思来理解.async是"异步"的简写,而await可以认为是async wait的简写,于是我们可以理解:
- async用于申明一个function是异步的
- await用于等待一个异步方法执行完成
async起什么作用
这个问题的关键在于,async函数是如何处理它的返回值的!
我们当然希望它可以直接通过return语句返回我们想要的结果,可如果是这样,似乎就没await什么事儿了.所以让我们写段代码来试试,看它到底会返回什么
async function testAsync() {
return 'hello';
}
const result = testAsync();
console.log(result) // promise{<resolved>:'hello'};
上面代码显示,最后输出的是一个promise对象.所以 async函数返回的是一个Promise对象,从 文档 中也可以得到这个信息.async函数(包括函数语句,函数表达式,Lambda表达式)会返回一个Promise对象.如果函数体中return一个直接量,async会把这个直接量通过Promise.resolve()封装成一个Promise对象.
async函数返回的是一个Promise对象,所以在最外层不能用await获取其返回值的情况下,我们当然应该用原来的方式:then()链来处理这个Promise对象,就像这样
testAsync().then( v => {
console.log(v) // hello
})
现在回过头来想下,如果async函数没有返回值,又改如何?很容易想到,它会返回
Promise.resolve(undefined)
联想一下Promise的特点 --- 无等待,所以在没有await的情况下执行async函数,它会立即执行,返回一个Promise对象,并且,绝不会阻塞后面的进程,这和普通返回Promise对象的函数并无不同
那么下一个关键点就在于await关键字了
await到底在等啥
一般来说,都认为await是在等待一个async函数完成.不过按照语法说明,await等待的是一个表达式,这个表达式的计算结果是一个Promise对象或其他说明值(换句话说,就是没有限定)
因为async返回的是一个Promise对象,所以await可以用于等待一个async函数的返回值---这也可以说是await在等async函数,但要清楚,它等的其实是一个返回值.注意await不仅仅用于等待Promise对象,它可以等待任意表达式的返回值,所以,await后面实际是可以接普通函数调用或者直接量的.所以下面这个示例是可以正确运行的
async function getSomething() {
return 'getSomething';
}
async function testAsync() {
return Promise.resolve('hello');
}
async function test() {
const v1 = getSomething();
const v2 = testAsync();
console.log(v1,v2)
}
test();
await等到了要等的,然后呢
await等到了它要等的东西,一个Promise对象,或者其他值,然后呢?我不得不先说,await是一个运算符,用于组成表达式,await表达式的运算结果取决于它等的东西
如果它等到的不是一个Promise对象,那await表达式的运算结果就是它等到的东西
如果它等到的是一个Promise对象,那await就忙起来了,await会阻塞后面的代码,等着Promise对象resolve,然后得到resolve的值,作为await表达式的运算结果
async和await帮我们干了啥
作个简单的比较
上面已经说明了,async会将后面的函数()的返回值封装成一个Promise,而await会等待这个Promise完成,并将resolve的值返回出来
现在举例,用setTimeout模拟耗时的异步操作,先来看看不用async和await会怎么写
function takelongTime() {
return new Promise( resolve => {
setTimeout(() = > {
console.log('loong_time_value')
},1000)
})
}
takeLongTime().then( v => {
console.log(v)
})
现在改用async和await以后, 会是这样:
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => {
console.log('long_time_value');
})
})
}
async function test() {
const v = await takeLongTime();
console.log(v);
}
test();
这里takeLongTime函数没有用async申明.实际上,takeLongTime本身就是返回的Promise对象,加不加async结果都一样.
上面两种方式,都是对异步调用的处理(实际都是对promise对象的处理)差别并不明显,甚至使用async和await,代码看起来更多,那么它的优势到底在哪里?
async和await的优势在于处理then链
单一的Promise链并不能发现async和await的优势,它的优势在于处理由多个Promise组成的then链
假设一个业务,分多个步骤完成,每一个步骤依赖上一个步骤的结果,我们依然用setTimeout来模拟异步操作
/**
*传入参数n,表示这个函数需要执行的时间(毫秒)
*得到的结果是n + 200,这个值将用于下一步骤
*/
function takeLongTime() {
return new Promise(resolve => {
setTimeout(() => resolve(n + 200),n)
})
}
function stemp1() {
console.log(`stemp1 with ${n}`)
return takeLongTime(n);
}
function stemp2() {
console.log(`stemp2 with ${n}`)
return takeLongTime(n);
}
function stemp3() {
console.log(`stemp3 with ${n}`)
return takeLongTime(n);
}
现在用promise方法来实现这三个步骤的处理
function doIt() {
console.time('doIt');
const time1 = 300;
stemp1(time1)
.then(time2 => stemp2(time2))
.then(time3 => stemp3(time3))
.then(result => {
console.log(`result is ${result}`)
console.timeEnd('doIt')
})
}
doIt();
输出结果 result 是 step3() 的参数 700 + 200 = 900。doIt() 顺序执行了三个步骤,
一共用了 300 + 500 + 700 = 1500 毫秒,和 console.time()/console.timeEnd() 计算的结果基本一致。
下面用async和await来实现
async function doIt() {
console.log('doIt');
const time1 = 300;
const time2 = await stemp1(time1);
const time3 = await stemp2(time2);
const result = await stemp3(time3);
console.log(`result is ${result}`)
console.timeEnd('doIt');
}
结果和之前的promsise实现是一致的,这样做的好处就是代码清晰了很多,几乎跟同步代码是一样的
原文摘自 《理解JavaScript 的 async/await》