共计 3640 个字符,预计需要花费 10 分钟才能阅读完成。
前言
异步操作一直是 JS 中不可或缺的一环,从最开始回调函数,到后面的 Promise,再到 ES2017 引入的 async 函数,异步操作逐渐进化,变得越来越简单方便,接下来就仔细看看在 ES2017 引入了 async 函数后,异步操作产生了哪些变化。
有什么用
以往我们使用异步函数,都是 async/await 一起用的,但是这回我准备拆开看,分别介绍 async 和 await 有什么用
async 作用
通常情况下使用 async 命令是因为函数内部有 await 命令,因为 await 命令只能出现在 async 函数里面,否则会报语法,这就是为什么 async/await 成对出现的原因,但是如果对一个普通函数单独加个 async 会是什么结果呢?来看个例子:
async function test () {
let a = 2
return a
}
const res = test()
console.log(res)
由例子可以 async 函数返回的是一个 Promise 对象,如果函数中有返回值,async 会把这个返回值通过 Promise.resole() 封装成 Promise 对象,要取这个值也很简单,直接通过 then() 就能取出,如例:
res.then(a => {console.log(a) // 2
})
在没有 await 的情况下,调用 async 函数,会立即执行,返回一个 Promise,那加上 await 会有什么不同呢?
await 作用
一般情况下,await 命令后面接的是一个 Promise 对象,等待 Promise 对象状态发生变化,得到返回值,但是也可以接任意表达式的返回结果,来看个例子:
function a () {return 'a'}
async function b () {return 'b'}
const c = await a()
const d = await b()
console.log(c, d) // 'a' 'b'
由例子可以看到 await 后面不管接的是什么表达式,都能等待到结果的返回,当等到不是 Promise 对象时,就将等到的结果返回,当等到的是一个 Promise 对象时,会阻塞后面的代码,等待 Promise 对象状态变化,得到对应的值作为 await 等待的结果,这里的阻塞指的是 async 内部的阻塞,async 函数的调用并不会阻塞
解决了什么问题
Promise…then 语法已经解决了以前一直存在的多层回调嵌套的问题,那问什么还要用 async/await 呢?要解答这个问题先来看一段 Promise 代码:
function login () {
return new Promise(resolve => {resolve('aaaa')
})
}
function getUserInfo (token) {
return new Promise(resolve => {if (token) {
resolve({isVip: true})
}
})
}
function getVipGoods (userInfo) {
return new Promise(resolve => {if (userInfo.isVip) {
resolve({
id: 'xxx',
price: 'xxx'
})
}
})
}
function showVipGoods (vipGoods) {console.log(vipGoods.id + '----' + vipGoods.price)
}
login()
.then(token => getUserInfo(token))
.then(userInfo => getVipGoods(userInfo))
.then(vipGoods => showVipGoods(vipGoods))
如例子所示,每一个 Promise 相当于一个异步的网络请求,通常一个业务流程需要多个网络请求,而且网络请求网络请求都依赖一个的请求结果,上例就是 Promise 模拟了这个过程,下面我们再来看看用 async/await 会有什么不同,如例:
async function call() {const token = await login()
const userInfo = await getUserInfo(token)
const vipGoods = await getVipGoods(userInfo)
showVipGoods(vipGoods)
}
call()
和 Promise 的 then 链调用相比,async/await 的调用更加清晰简单,和同步代码一样
带来了什么问题
使用 async/await 我们经常会忽略一个问题,同步执行带来的时间累加,会导致程序变慢,有时候我们的代码可以写成并发执行,但是由于 async/await 做成了继发执行,来看一个例子:
function test () {
return new Promise(resolve => {setTimeout(() => {console.log('test')
resolve()}, 1000)
})
}
function test1 () {
return new Promise(resolve => {setTimeout(() => {console.log('test1')
resolve()}, 1000)
})
}
function test2 () {
return new Promise(resolve => {setTimeout(() => {console.log('test2')
resolve()}, 1000)
})
}
async function call () {await test()
await test1()
await test2()}
call ()
上面代码继发执行,所花时间是:
实际上,这段代码执行顺序,我并不关心,继发执行就浪费大量执行时间,下面改成并发执行:
function call () {Promise.all([test(), test1(), test2()])
}
call()
所花时间:
因此在使用 async/await 时需要特别注意这一点
循环中的小问题
在写 JS 循环时,JS 提供了许多好用数组 api 接口,forEach 就是其中一个,但是碰上了 async/await,可能就悲剧了,得到了不是你想要的结果,来看一个例子:
function getUserInfo (id) {
return new Promise(resolve => {setTimeout(() => {
resolve({
id: id,
name: 'xxx',
age: 'xxx'
})
}, 200)
})
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
users.forEach(async user => {let info = await getUserInfo(user.id)
userInfos.push(info)
})
console.log(userInfos) // []
上面这段代码是不是很熟悉,模拟获取多个用户的用户信息,然后得到一个用户信息数组,但是很遗憾,上面的 userInfos 得到的是一个空数组,上面这段代码加上了 async/await 后,forEach 循环就变成了异步的,因此不会等到所有用户信息都请求完才打印 userInfos,想要等待结果的返回再打印,还是要回到老式的 for 循环,来看代码:
function getUserInfo (id) {
return new Promise(resolve => {setTimeout(() => {
resolve({
id: id,
name: 'xxx',
age: 'xxx'
})
}, 200)
})
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
async function call() {for (user of users) {let info = await getUserInfo(user.id)
userInfos.push(info)
}
console.log(userInfos)
}
call()
上面这种写法是继发式的,也就是会等前面一个任务执行完,再执行下一个,但是也许你并不关心执行过程,只要拿到想要的结果就行了,这时并发式的效率会更高,来看代码:
function getUserInfo (id) {
return new Promise(resolve => {setTimeout(() => {
resolve({
id: id,
name: 'xxx',
age: 'xxx'
})
}, 200)
})
}
const users = [{id: 1}, {id: 2}, {id: 3}]
let userInfos = []
const promises = users.map(user => getUserInfo(user.id))
Promise.all(promises).then(res => {
userInfos = res
console.log(userInfos)
})
由上面例子可以看到并发执行的效率要高得多
总结
此篇文章 async/await 的用法和经常遇到的一些问题做了简单的总结,希望能对大家在使用的时候有所帮助。
如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞