async/await:使用同步的方式去寫異步代碼

(一)async/await 使用了 Generator 和 Promise 兩種技術

1. Generator 生成器函數

生成器函數是一個帶星號函數,而且是可以暫停執行和恢復執行的函數。

(1)在生成器函數內部執行一段代碼,如果遇到 yield 關鍵字,那麼 JavaScript 引擎將返回關鍵字後面的內容給外部,並暫停該函數的執行。

(2)外部函數可以通過 next 方法恢複函數的執行。

要搞懂函數爲何能暫停和恢復,那你首先要了解協程的概念。協程是一種比線程更加輕量級的存在。可以把協程看成是跑在線程上的任務,一個線程上可以存在多個協程,但是在線程上同時只能執行一個協程,比如當前執行的是 A 協程,要啓動 B 協程,那麼 A 協程就需要將主線程的控制權交給 B 協程,這就體現在 A 協程暫停執行,B 協程恢復執行;同樣,也可以從 B 協程中啓動 A 協程。通常,如果從 A 協程啓動 B 協程,我們就把 A 協程稱爲 B 協程的父協程。

例子:

// 聲明一個構造器函數
    function* generator () {
      console.log("開始執行第一段") 
      yield 1 

      console.log("開始執行第二段") 
      yield {name: 'ss'}

      console.log("開始執行第三段") 
      yield 3

      console.log("執行結束") 
      return 4
    }
    // 聲明一個迭代器
    let gen = generator()
    // 執行第一步
    console.log(gen.next()) 

返回一個對象,value 的值是 yield 後的值,done 表示該迭代器是否執行到最後一步

// 執行第二步
console.log(gen.next()) 

 

// 執行第三步
console.log(gen.next()) 

// 執行第四步
console.log(gen.next())

此時的done 變爲true ,表示該迭代器執行完畢。

如果再next

console.log(gen.next())

則value 返回 undefined,done 爲 true

注意:如果 yield 後沒有值 ,value也是undefined

2. 消息傳遞

yield... 和 next(...) 這一對組合起來,在生成器的執行過程中構成了一個雙向消息傳遞系統。

next()的參數是傳給生成器的,可以用來調節生成器的行爲。而yield的值是返回給next()的調用者的。兩者的區別如下:

next()參數 : 調用者 ---> 生成器
yield 表達式:生成器 ---> 調用者

next()方法帶一個參數,該參數就會被當作上一個yield表達式的返回值。例子如下:

function* main(x) {
  const y = x * (yield 12)
  return y
}
const it = main(6) // step1
console.log(it.next()) // step 2
console.log(it.next(7)) // step3

step1: 向生成器中傳入參數 6 ,則 向賦值爲 6

step2: value 爲 yield 後的值 value 爲 12

step3: next() 向生成器中傳入值 7,所以(yield 12)整體被看作 7 ,y 爲 42

結果運行如下:

說明:

(1)第一個 next 是啓動生成器的,沒有上一個yield,所以第一個next傳參無效,該參數會被忽略。

(2)如果不帶參數,則表示上一個參數返回undefined

3. Generator、Promise 實現完美異步

當請求一個接口時:

<script>
  function getData () {
    return fetch('https://www.fastmock.site/mock/e9463cacabbb7690593cdbaf53f1480d/clean/test/a')
  }
  function* dataCallBack() {
    try {
      let result = yield getData()
    } catch(err) {
      console.log(err)
    }
  }
  let it = dataCallBack()
  // {value: Promise, done: false}
  let promise = it.next().value
  promise.then((res) => {console.log(res)})
</script>

當請求多個接口時:

<script>
    // generator + Promise 實現 await/async 

   // 1. Promise
    function tick (time) {
      return new Promise ((resolve, reject) => {
        setTimeout(() => {
          resolve('當前時間:' + new Date())
        }, time)
      })
    }

    // 2. generator
    function* gene() {
      let result1 = yield tick(3000)
      console.log('result1', result1)
      let result2 = yield tick(2000)
      console.log('result2', result2)
    }
    // 驅動函數
    let run = (generator, res) => {
      let start = generator.next(res)
      if (start.done) return
      start.value.then((res) => {
        run(generator, res) // 回調
      })
    }
    // 執行
    run(gene())

(二)async 是什麼

根據 MDN 定義,async 是一個通過異步執行並隱式返回 Promise 作爲結果的函數。

<script>
    async function a () {
      return 3
    }
    console.log(a()) // Promise {<resolved>: 3}
  </script>

async 返回一個Promise

async function foo() {
    console.log(1)
    let a = await 100
    console.log(a)
    console.log(2)
}
console.log(0)
foo()
console.log(3)

執行結果:0 1 3 100 2

知識點:Promise  立即執行 1 輸出在3 前面

總結:Promise 的編程模型依然充斥着大量的 then 方法,雖然解決了回調地獄的問題,但是在語義方面依然存在缺陷,代碼中充斥着大量的 then 函數,這就是 async/await 出現的原因。

1. generator 函數是如何暫停執行程序的?
答案是通過協程來控制程序執行。
generator 函數是一個生成器,執行它會返回一個迭代器,這個迭代器同時也是一個協程。一個線程中可以有多個協程,但是同時只能有一個協程在執行。線程的執行是在內核態,是由操作系統來控制;協程的執行是在用戶態,是完全由程序來進行控制,通過調用生成器的next()方法可以讓該協程執行,通過yield關鍵字可以讓該協程暫停,交出主線程控制權,通過return 關鍵字可以讓該協程結束。協程切換是在用戶態執行,而線程切換時需要從用戶態切換到內核態,在內核態進行調度,協程相對於線程來說更加輕量、高效。
2. async function實現原理?
async function 是通過 promise + generator 來實現的。generator 是通過協程來控制程序調度的。
​在協程中執行異步任務時,先用promise封裝該異步任務,如果異步任務完成,會將其結果放入微任務隊列中,然後通過yield 讓出主線程執行權,繼續執行主線程js,主線程js執行完畢後,會去掃描微任務隊列,如果有任務則取出任務進行執行,這時通過調用迭代器的next(result)方法,並傳入任務執行結果result,將主線程執行權轉交給該協程繼續執行,並且將result賦值給yield 表達式左邊的變量,從而以同步的方式實現了異步編程。
所以說到底async function 還是通過協程+微任務+瀏覽器事件循環機制來實現的。

參考文章:https://www.jianshu.com/p/83da0901166f

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章