async、await和generator函式內部原理

迪迪同學發表於2019-07-30

對async、await的理解,內部原理

async 是Generator函式的語法糖,並對Generator函式進行了改進

  1. async的實現就是將Generator 函式和自動執行器,包裝在一個函式裡。
async function fn(args) {
  // ...
}

// 等同於

function fn(args) {
  return spawn(function* () {
    // ...
  });
}
複製程式碼

spawn 函式就是自動執行器, 下面給出spawn函式的實現

function spawn(genF){
    return new Promise((resolve, reject)=>{
        const gen = genF() // 先將Generator函式執行下,拿到遍歷器物件
        function step(nextF) {
            let next
            try {
                next = nextF()
            } catch(e){
                return reject(e)
            }
            if(next.done){
                return resolve(next.value)
            }
            Promise.resolve(next.value).then((v)=>{
                step(()=>{return gen.next(v)})
            }, (e)=>{
                step(()=>{return gen.throw(e)})
            })
        }
        step(()=> {return gen.next(undefinex)})
    })
}
複製程式碼
  1. 更好的語義

async 和 await, 比起星號和yield,語義更加清楚,async表示函式裡面有非同步操作,await表示緊跟在後面的表示式需要等待結果。

  1. 更廣的適用性

co模組約定,yield命令後面只能是Thunk函式或者Promise物件, 而async函式的await後面,可以是Promise和原始型別值(數值、字串和布林值,但這時會自動轉成立即 resolved 的 Promise 物件, 檢視spawn函式中Promise.resolve(next.value))

  1. 返回值是Promise

比Generator函式的返回值是Iterator物件方便,可以使用then方法指定下一步操作

你可能會問,async是Generator函式的語法糖,那什麼是Generator函式呢?

Generator函式語法:重點是*和關鍵字yield

function* helloWorldGenerator() {
  yield 'hello';
  yield 'world';
  return 'ending';
}
var hw = helloWorldGenerator();
複製程式碼

執行Generator函式helloWorldGenerator的話,並不執行,而是返回一個迭代器物件,下一步,必須呼叫遍歷器物件的next方法,使得指標移向下一個狀態

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'world', done: false }

hw.next()
// { value: 'ending', done: true }

hw.next()
// { value: undefined, done: true }
複製程式碼

yield 由於 Generator 函式返回的遍歷器物件,只有呼叫next方法才會遍歷下一個內部狀態,所以其實提供了一種可以暫停執行的函式。yield表示式就是暫停標誌。

Generator與協程

協程是一種程式執行的方式,可以用單執行緒實現,也可以用多執行緒實現。

  • 協程概念:

一個執行緒(或函式)執行到一半,可以暫停執行,將執行權交給另一個執行緒(或函式),等到稍後收回執行權的時候,再恢復執行。這種可以並行執行、交換執行權的執行緒(或函式),就稱為協程。

  • 協程與普通執行緒的差異:
  1. 普通執行緒是搶先式的,會爭奪cpu資源,而協程是合作的,
  2. next 同一時間,可以有多個普通執行緒執行,而協程則只有一個在執行,其他協程則處在暫停狀態。

Generator 函式是 ES6 對協程的實現,但是不完全,Generator 函式被稱為“半協程”(semi-coroutine),意思是隻有 Generator 函式的呼叫者,才能將程式的執行權還給 Generator 函式。如果是完全執行的協程,任何函式都可以讓暫停的協程繼續執行。

Generator 與上下文

Generator 函式, 它執行產生的上下文環境,一旦遇到yield命令,就會暫時退出堆疊,但是並不消失,裡面的所有變數和物件會凍結在當前狀態。等到對它執行next命令時,這個上下文環境又會重新加入呼叫棧,凍結的變數和物件恢復執行。

Generator 的實現

具體請參考alloyteam的文章

js的Generator並非由引擎從底層提供額外的支援,而是通過程式碼的編寫,來實現的函式暫停、按序執行。 在實現Generator過程中有兩個關鍵點,一是要儲存函式的上下文資訊,二是實現一個完善的迭代方法,使得多個 yield 表示式按序執行,從而實現生成器的特性。 大體上就是使用由 switch case 組成的狀態機模型中, 除此之外,利用閉包技巧,儲存生成器函式上下文資訊。

Regenerator 通過工具函式將生成器函式包裝,為其新增如 next/return 等方法。同時也對返回的生成器物件進行包裝,使得對 next 等方法的呼叫,最終進入由 switch case 組成的狀態機模型中。除此之外,利用閉包技巧,儲存生成器函式上下文資訊。

上述過程與 C#中 yield 關鍵字的實現原理基本一致,都採用了編譯轉換思路,運用狀態機模型,同時儲存函式上下文資訊,最終實現了新的 yield 關鍵字帶來的新的語言特性。

這個連結可以檢視轉換後的程式碼:這個線上地址

相關文章