對async、await的理解,內部原理
async 是Generator函式的語法糖,並對Generator函式進行了改進
- 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)})
})
}
複製程式碼
- 更好的語義
async 和 await, 比起星號和yield,語義更加清楚,async表示函式裡面有非同步操作,await表示緊跟在後面的表示式需要等待結果。
- 更廣的適用性
co模組約定,yield命令後面只能是Thunk函式或者Promise物件, 而async函式的await後面,可以是Promise和原始型別值(數值、字串和布林值,但這時會自動轉成立即 resolved 的 Promise 物件, 檢視spawn函式中Promise.resolve(next.value))
- 返回值是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與協程
協程是一種程式執行的方式,可以用單執行緒實現,也可以用多執行緒實現。
- 協程概念:
一個執行緒(或函式)執行到一半,可以暫停執行,將執行權交給另一個執行緒(或函式),等到稍後收回執行權的時候,再恢復執行。這種可以並行執行、交換執行權的執行緒(或函式),就稱為協程。
- 協程與普通執行緒的差異:
- 普通執行緒是搶先式的,會爭奪cpu資源,而協程是合作的,
- 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 關鍵字帶來的新的語言特性。
這個連結可以檢視轉換後的程式碼:這個線上地址