错误的理解引起的bug async await 执行顺序

今天有幸好碰到一个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 。

控制台运行结果:

错误的理解引起的bug async 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 执行顺序关键点

  1. 事件循环机制
  2. 回调队列
  3. 执行环境栈、入栈、出栈
  4. Promise 的构造函数是立即执行,但是他的成功、失败的回调函数是一个异步执行的回调
  5. Promise 的回调优先于 setTimeout 的任务队列
  6. async 返回promise 对象
  7. await 表达式的作用和返回值

总结

1、js 是单线程(同时只能做一件事情),在js引擎内部异步的处理是跟事件循环机制、以及回调队列有关

2、构造的promise 对象是立即执行传入的function

3、async function 是返回一个promise 对象

4、await 操作符会把表达式的结果进行解析成promise 对象

上一篇:Plugin with id 'com.novoda.bintray-release' not found的解决方法


下一篇:Spring连接MySQL、Oracle和SQL Server的数据库运动连接属性