(一)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