目录
背景
上文提到的Promise用来处理异步操作结果,配合then注册回调函数, 形成链式调用,让代码易读。除此之外, JS还提供了一个语法糖async/await对Promise使用进行封装,可使异步代码看起来像同步代码的样式,使得代码容易阅读和维护。本文介绍Async/await的使用和注意事项。
Await/async与Promise比较
/**
* 加载活动列表(其中先加载群组以得到活动的头像)
*/
fetchGroupAndActivities: function(){
if(this.data.isLogin) {
var that = this
getGroups() //先加载群组列表的头像。
.then((res)=>{
if(res.data.code == "10000") {
...
return getActivities() //其次,加载活动列表
}
})
.then((res)=>{ //链式调用,处理活动列表数据。
if (res.data.code == "10000") {
...
}
})
.catch((err) => { //统一捕获异常。 上面then中的任意回调发送异常,会直接中断调用链,在这里处理。
console.log("get act and group failed...")
})
使用Await/async重构后的代码形式
/**
* 加载活动列表(其中先加载群组以得到活动的头像)
*/
fetchGroupAndActivities: async function(){
if(this.data.isLogin) {
try {
var res = await getGroups() //1. 异步加载群组列表。
if(res.data.code == "10000") {
...
var activitiesRes= await getActivities() //2.异步加载活动列表
if (activitiesRes.data.code == "10000") {
...//处理活动列表数据
}
}
} catch(e) {
console.log("get act and group failed...e = " + e) //统一处理异常
}
}
- 如果略过async和await,上面的代码风格看起来和同步代码一样。
- async声明的函数,一定会返回一个promise,也就是说这个函数返回后,可以调用.then进行链式调用,如下代码代码所示:
fetchGroupAndActivities() .then(() => { console.log("address groups and activities.") })
- await关键字用来声明一个返回promise的异步操作。它可以挂起函数,允许其他函数代码得到执行,直到异步操作返回结果(promise为fulfiled状态)。async函数继续从await语句下一行开始执行。
Await/async搭配Promise.all()使用
需求:获取群组列表和活动列表
分析:两组数据源,全部的结果返回后,再依次消费这些结果。
代码:
async function fetchIndexList() {
var results = await Promise.all([getGroups, getActivities])
return results //这里被js自动封装成Promise
}
可以看到如果用Promise.all()API,代码行数少。如果不用Promise,改怎么写?
async function fetchIndexList() {
var groups = getGroups();
var activities = getActivities();
var result1 = await groups
var result2 = await activities
return [result1, result2]//这里被js自动封装成Promise
}
注意事项
- 关键字await必须在async异步函数内使用。
- async函数的返回值,是一个promise对象。如果用户没有返回或者返回普通对象,js环境打包时会自动为你包装一个promise的对象。
代码执行顺序
- async函数执行时,在遇到await关键字之前,它与调用它的函数,都是同步运行的。
- 当遇到await关键字,async函数将被挂起,且释放cpu控制权,允许其他函数继续执行, 直到await声明的异步函数返回结果。一旦完成,程序将从await语句下一行代码开始执行。
缺陷
- await不正确使用带来的阻塞问题
通过一个计时器代码来测量await可能带来的速度问题。
1. 定义计时器A
function A(){
return new Promise((resovle, reject) => {
setTimeout({
resolve()
}, 3000)
})
}
2. 定义计时器B
function B(){
return new Promise((resovle, reject) => {
setTimeout({
resolve()
}, 3000)
})
}
3. 启动两个计数器,统计总共消耗多少时间
async function startTimer(){
var startTime = new Date()
var resultA = await A()
var resultB = await B()
setTimeout({
var finishTime = new Date()
var totalTime = finishTime - startTime
console.log("totalTime = " + totalTime)
},3000)
}
startTimer()
上述代码执行后,计时器总共耗时大于9s。 await标记的异步任务会一个等待前一个await标记的任务执行完毕,如果await依赖过多,产生的执行时间可能更长。有时候你只是想让promise同时执行,有没有一种办法减少时间消耗?解决办法:
async function startTimer(){
var startTime = new Date()
var resultA = A()
var resultB = B()
await resultA
await resultB
setTimeout({
var finishTime = new Date()
var totalTime = finishTime - startTime
console.log("totalTime = " + totalTime)
},3000)
}
startTimer()
把代表计时器的Promise保存在变量里面,然后再去等待他们的结果。
这样看起来三个计时器Promise是同时启动,它们也将同时完成。最终输出的totalTime大约只有3s。
2. await必须和async搭配使用,违背的代码设计原则, async和await耦合起来了,开发者容易忘记声明async,造成错误。
总结
本文介绍了JS异步编程中的语法糖await/async来包装Promise,使得异步代码看起来像同步代码的风格,让代码变得简洁。同时介绍了async函数执行的流程,以及使用await可能带来的问题。