今天有幸好碰到一个bug,让我知道了之前我对await async 的理解有点偏差。
错误的理解
之前我一直以为 await 后面的表达式,如果是直接返回一个具体的值就不会等待,而是继续执行async function 中的函数, 如下demo:
method () {
getParams () {
let params = {}
if (this.serachFrom.time !== 0) {
params.month = this.serachFrom.time.substr(5, 2)
params.year = this.serachFrom.time.substring(0, 4)
}
return params
},
async testNoAwait () {
console.log('run testNoAwait')
return 'this is no await'
},
async testAsync () {
console.log('run testAsync')
let params = this.getParams()
const data = await this.$store.dispatch('initSchemeTimeTest', params)
return data
},
async test () {
console.log('test start')
const v1 = await this.testNoAwait()
console.log(v1)
const v2 = await this.testAsync()
console.log(v2)
console.log(v1, v2)
}
},
created () {
console.log('this is run created ')
this.test()
console.log('test last ...')
console.log('test end ...')
}
如上程序我之前认为 await this.testNoAwait() 会直接执行完不会等待,继续执行 console.log(v1),如果这样那么是一个错误的理解。
实际上MDN描述的暂停执行,并不是真正的暂停,而是让出了线程(跳出async函数体)然后继续执行后面的语句。
完整 demo code
vue created
async created () {
console.log('this is run created ')
this.test()
// let data = this.test()
// console.log(data)
console.log('test last ...')
console.log('test end ...')
this.testSayHello()
}
vue methods
testSayHello () {
console.log('this is run hello')
},
getParams () {
let params = {}
if (this.serachFrom.time !== 0) {
params.month = this.serachFrom.time.substr(5, 2)
params.year = this.serachFrom.time.substring(0, 4)
}
return params
},
testNoAwait () {
console.log('run testNoAwait')
return 'this is no await'
},
async testAsync () {
console.log('run testAsync')
let params = this.getParams()
const data = await this.$store.dispatch('initSchemeTimeTest', params)
return data
},
async test () {
console.log('test start')
const v1 = await this.testNoAwait()
console.log(v1)
const v2 = await this.testAsync()
console.log(v2)
console.log(v1, v2)
}
vuex 中
// actions
async initSchemeTimeTest ({commit, state, dispatch}, params) {
console.log('run initSchemeTimeTest')
const data = await schemeListTest(params)
console.log('开始返回结果')
commit(types.SCHEME_DATA_TIME_LIST, data)
return data
}
services api 中
注意在 testAsync 中 dispatch 了 initSchemeTimeTest,然后在调用了服务端的 schemeListTest
export async function schemeListTest (params) {
console.log('this is run server')
const data = await postTest(`/provid/spot/dailydeclareschemestatus/list`, params)
return data
}
common 中封装的 axiosServer
export function postTest (url, params) {
return new Promise(async (resolve, reject) => {
try {
console.log('this is run common')
const {
data:
{
respHeader,
respBody
}
} = await axiosServer({
url,
type: 'post',
params: {
reqBody: params
}
})
if (respHeader.needLogin && process.env.NODE_ENV !== 'development') {
Message.error(respHeader.message)
location.href = condition.frontDomain + `/login?redirect=${encodeURI(condition.frontDomain + '/spot/race')}`
reject(respHeader.message)
}
if (respHeader.resultCode === 0) {
resolve(respBody || respHeader.message)
} else {
if (respHeader.resultCode === 21050 && respBody) {
Message.error(respHeader.message)
resolve(respBody)
} else if (respHeader.message === '您没有该应用的权限') {
location.href = 'frame.huidiancloud.com'
} else {
Message.error(respHeader.message)
reject(respHeader.message)
}
}
} catch (e) {
reject(e)
Message.error('系统繁忙,请稍后再试!')
}
})
}
如果按照之前的理解那么这个应该是输出了 run testNoAwait 之后继续输出 this is no await 。
控制台运行结果:
执行顺序
js是单线程(同时只能干一件事情),
以上测试的关键点在于当程序碰到await 时,把后面的表达式执行一次,然后把resolve 函数或者reject 函数(await 操作符会把表达式的结果解析成promise 对象) push 回调队列,接着跳过当前这个async function ,执行async function 后面的代码,如上面代码中,执行 this.testNoAwait() 之后就跳过 this.test()这个方法,执行了
console.log('test last ...')
console.log('test end ...')
this.testSayHello()
至于什么时候知道这个promise 对象的状态,这就是事件循环的事情了,监听到这个异步的状态事件改变时,如果执行环境栈是空的那么就会执行取出回调队列中的回调,推入执行环境栈,然后继续async function 后面的语句。
vue 开始执行created 生命周期
输出:this is run created
输出:test start
执行:testNoAwait // 关键
输出 :run testNoAwait 之后 跳过 test() 函数 执行created 后面的语句
输出:test last ... 、test end ... 、this is run hello
程序回到
const v1 = await this.testNoAwait()
如果监听到这个异步事件完成 则开始执行 后面的代码所以会
输出:this is no await
下面这个 await 跟上面同理
const v2 = await this.testAsync()
await 后面的表达式执行一次,如果里面存在await 也是同理继续执行下去,执行完之后,跳过这个async function 等到异步操作完成了继续回到 const v2 这里执行。
这里需要注意的是在common 中的postTest 中构造的Promise 对象是立即执行传入的function 所以在 services api 输出了 this is run server 之后接着输出 this is run common
因为上面的列子不是很方便看,所以我写了一个简单的测试 :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta content="telephone=no,email=no" name="format-detection">
<meta name="App-Config" content="fullscreen=yes,useHistoryState=yes,transition=yes">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>async await demo</title>
</head>
<body>
<h1>async await demo</h1>
</body>
<script> async function async1() {
console.log('async1 start')
await async2()
console.log('async1 end')
}
async function async2() {
console.log('async2')
await async3()
}
async function async3() {
console.log('async3')
await async4()
console.log('async4 end')
}
async function async4() {
return new Promise(function (resolve, reject) {
console.log('async4')
resolve()
})
}
console.log('script start')
setTimeout(function () {
console.log('setTimeout')
}, 0)
async1();
new Promise(function (resolve) {
console.log('promise1')
resolve();
}).then(function () {
console.log('promise2')
})
console.log('script end') // script start async1 start async2 async3 async4 promise1 script end promise2 async4 end async1 end setTimeout </script>
</html>
async awiat 执行顺序关键点
- 事件循环机制
- 回调队列
- 执行环境栈、入栈、出栈
- Promise 的构造函数是立即执行,但是他的成功、失败的回调函数是一个异步执行的回调
- Promise 的回调优先于 setTimeout 的任务队列
- async 返回promise 对象
- await 表达式的作用和返回值
总结
1、js 是单线程(同时只能做一件事情),在js引擎内部异步的处理是跟事件循环机制、以及回调队列有关
2、构造的promise 对象是立即执行传入的function
3、async function 是返回一个promise 对象
4、await 操作符会把表达式的结果进行解析成promise 对象