1. 程式人生 > >錯誤的理解引起的bug async await 執行順序

錯誤的理解引起的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 執行順序關鍵點

  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 物件