錯誤的理解引起的bug async await 執行順序
錯誤的理解引起的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 。
控制檯執行結果:
執行順序
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 物件