錯誤的理解引發的bug async await 執行順序

今天有幸虧碰到一個bug,讓我知道了以前我對await async 的理解有點誤差。html

錯誤的理解

以前我一直覺得  await 後面的表達式,若是是直接返回一個具體的值就不會等待,而是繼續執行async function 中的函數, 以下demo:vue

 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),若是這樣那麼是一個錯誤的理解。ios

實際上MDN描述的暫停執行,並非真正的暫停,而是讓出了線程(跳出async函數體)而後繼續執行後面的語句。web

完整 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,而後在調用了服務端的 schemeListTestvuex

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 。axios

控制檯運行結果:

執行順序

js是單線程(同時只能幹一件事情),api

以上測試的關鍵點在於當程序碰到await 時,把後面的表達式執行一次,而後把resolve 函數或者reject 函數(await 操做符會把表達式的結果解析成promise 對象) push 回調隊列,接着跳過當前這個async function ,執行async function 後面的代碼,如上面代碼中,執行 this.testNoAwait() 以後就跳過 this.test()這個方法,執行了promise

console.log('test last ...')
console.log('test end ...')
this.testSayHello()

至於何時知道這個promise 對象的狀態,這就是事件循環的事情了,監聽到這個異步的狀態事件改變時,若是執行環境棧是空的那麼就會執行取出回調隊列中的回調,推入執行環境棧,而後繼續async function 後面的語句。app

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 表達式的做用和返回值

總結

一、js 是單線程(同時只能作一件事情),在js引擎內部異步的處理是跟事件循環機制、以及回調隊列有關

二、構造的promise 對象是當即執行傳入的function

三、async function 是返回一個promise 對象

四、await 操做符會把表達式的結果進行解析成promise 對象

相關文章
相關標籤/搜索