前兩年大量的在寫
Generator
+co
,用它來寫一些相似同步的代碼
但實際上,Generator
並非被造出來幹這個使的,否則也就不會有後來的async
、await
了
Generator
是一個能夠被暫停的函數,而且什麼時候恢復,由調用方決定
但願本文能夠幫助你理解Generator
到底是什麼,以及怎麼用javascript
放一張圖來表示我對Generator
的理解:前端
我所理解的Generator咖啡機大概就是這麼的一個樣子的:java
gen.next()
),機器開始磨咖啡豆、煮咖啡、接下來就獲得咖啡了yield
)拿Generator
將上述咖啡機實現一下:git
function * coffeeMachineGenerator (beans) {
do {
yield cookCoffee()
} while (--beans)
// 煮咖啡
function cookCoffee () {
console.log('cooking')
return 'Here you are'
}
}
// 往咖啡機放咖啡豆
let coffeeMachine = coffeeMachineGenerator(10)
// 我想喝咖啡了
coffeeMachine.next()
// 我在3秒後還會喝咖啡
setTimeout(() => {
coffeeMachine.next()
}, 3 * 1e3)
複製代碼
代碼運行後,咱們首先會獲得一條cooking
的log
,
而後在3s
後會再次獲得一條log
。es6
這就解釋了Generator
是什麼:
一個能夠暫停的迭代器
調用next
來獲取數據(咱們本身來決定是否什麼時候煮咖啡)
在遇到yield
之後函數的執行就會中止(接滿了一杯,閥門關閉)
咱們來決定什麼時候運行剩餘的代碼next
(何時想喝了再去煮)github
這是Generator
中最重要的特性,咱們只有在真正須要的時候才獲取下一個值,而不是一次性獲取全部的值ajax
聲明Generator
函數有不少種途徑,最重要的一點就是,在function
關鍵字後添加一個*
dom
function * generator () {}
function* generator () {}
function *generator () {}
let generator = function * () {}
let generator = function* () {}
let generator = function *() {}
// 錯誤的示例
let generator = *() => {}
let generator = ()* => {}
let generator = (*) => {}
複製代碼
或者,由於是一個函數,也能夠做爲一個對象的屬性來存在:異步
class MyClass {
* generator() {}
*generator2() {}
}
const obj = {
*generator() {}
* generator() {}
}
複製代碼
一個Generator
函數經過調用兩次方法,將會生成兩個徹底獨立的狀態機
因此,保存當前的Generator
對象很重要:async
function * generator (name = 'unknown') {
yield `Your name: ${name}`
}
const gen1 = generator()
const gen2 = generator('Niko Bellic')
gen1.next() // { value: Your name: unknown , done: false}
gen2.next() // { value: Your name: Niko Bellic, done: false}
複製代碼
最經常使用的next()
方法,不管什麼時候調用它,都會獲得下一次輸出的返回對象(在代碼執行完後的調用將會始終返回{value: undefined, done: true}
)。
next
總會返回一個對象,包含兩個屬性值:
value
:yield
關鍵字後邊表達式的值
done
:若是已經沒有yield
關鍵字了,則會返回true
.
function * generator () {
yield 5
return 6
}
const gen = generator()
console.log(gen.next()) // {value: 5, done: false}
console.log(gen.next()) // {value: 6, done: true}
console.log(gen.next()) // {value: undefined, done: true}
console.log(gen.next()) // {value: undefined, done: true} -- 後續再調用也都會是這個結果
複製代碼
Generator
函數是一個可迭代的,因此,咱們能夠直接經過for of
來使用它。
function * generator () {
yield 1
yield 2
return 3
}
for (let item of generator()) {
item
}
// 1
// 2
複製代碼
return
不參與迭代
迭代會執行全部的yield
,也就是說,在迭代後的Generator
對象將不會再返回任何有效的值
咱們能夠在迭代器對象上直接調用return()
,來終止後續的代碼執行。
在return
後的全部next()
調用都將返回{value: undefined, done: true}
function * generator () {
yield 1
yield 2
yield 3
}
const gen = generator()
gen.return() // {value: undefined, done: true}
gen.return('hi') // {value: "hi", done: true}
gen.next() // {value: undefined, done: true}
複製代碼
在調用throw()
後一樣會終止全部的yield
執行,同時會拋出一個異常,須要經過try-catch
來接收:
function * generator () {
yield 1
yield 2
yield 3
}
const gen = generator()
gen.throw('error text') // Error: error text
gen.next() // {value: undefined, done: true}
複製代碼
yield
的語法有點像return
,可是,return
是在函數調用結束後返回結果的
而且在調用return
以後不會執行其餘任何的操做
function method (a) {
let b = 5
return a + b
// 下邊的兩句代碼永遠不會執行
b = 6
return a * b
}
method(6) // 11
method(6) // 11
複製代碼
function * yieldMethod(a) {
let b = 5
yield a + b
// 在執行第二次`next`時,下邊兩行則會執行
b = 6
return a * b
}
const gen = yieldMethod(6)
gen.next().value // 11
gen.next().value // 36
複製代碼
yield*
用來將一個Generator
放到另外一個Generator
函數中執行。
有點像[...]
的功能:
function * gen1 () {
yield 2
yield 3
}
function * gen2 () {
yield 1
yield * gen1()
yield 4
}
let gen = gen2()
gen.next().value // 1
gen.next().value // 2
gen.next().value // 3
gen.next().value // 4
複製代碼
yield
是能夠接收返回值的,返回值能夠在後續的代碼被使用
一個詭異的寫法
function * generator (num) {
return yield yield num
}
let gen = generator(1)
console.log(gen.next()) // {value: 1, done: false}
console.log(gen.next(2)) // {value: 2, done: false}
console.log(gen.next(3)) // {value: 3, done: true }
複製代碼
咱們在調用第一次next
時候,代碼執行到了yield num
,此時返回num
而後咱們再調用next(2)
,代碼執行的是yield (yield num)
,而其中返回的值就是咱們在next
中傳入的參數了,做爲yield num
的返回值存在。
以及最後的next(3)
,執行的是這部分代碼return (yield (yield num))
,第二次yield
表達式的返回值。
上邊的全部示例都是創建在已知次數的Generator
函數上的,但若是你須要一個未知次數的Generator
,僅須要建立一個無限循環就夠了。
好比咱們將實現一個隨機數的獲取:
function * randomGenerator (...randoms) {
let len = randoms.length
while (true) {
yield randoms[Math.floor(Math.random() * len)]
}
}
const randomeGen = randomGenerator(1, 2, 3, 4)
randomeGen.next().value // 返回一個隨機數
複製代碼
那個最著名的斐波那契數,基本上都會選擇使用遞歸來實現
可是再結合着Generator
之後,就可使用一個無限循環來實現了:
function * fibonacci(seed1, seed2) {
while (true) {
yield (() => {
seed2 = seed2 + seed1;
seed1 = seed2 - seed1;
return seed2;
})();
}
}
const fib = fibonacci(0, 1);
fib.next(); // {value: 1, done: false}
fib.next(); // {value: 2, done: false}
fib.next(); // {value: 3, done: false}
fib.next(); // {value: 5, done: false}
fib.next(); // {value: 8, done: false}
複製代碼
再次重申,我我的不認爲async/await是Generator的語法糖。。
若是是寫前端的童鞋,基本上都會遇處處理分頁加載數據的時候
若是結合着Generator
+async
、await
,咱們能夠這樣實現:
async function * loadDataGenerator (url) {
let page = 1
while (true) {
page = (yield await ajax(url, {
data: page
})) || ++page
}
}
// 使用setTimeout模擬異步請求
function ajax (url, { data: page }) {
return new Promise((resolve) => {
setTimeout(_ => {
console.log(`get page: ${page}`);
resolve()
}, 1000)
})
}
let loadData = loadDataGenerator('get-data-url')
await loadData.next()
await loadData.next()
// force load page 1
await loadData.next(1)
await loadData.next()
// get page: 1
// get page: 2
// get page: 1
// get page: 2
複製代碼
這樣咱們能夠在簡單的幾行代碼中實現一個分頁控制函數了。
若是想要從加載特定的頁碼,直接將page
傳入next
便可。
Generator
還有更多的使用方式,(實現異步流程控制、按需進行數據讀取) 我的認爲,Generator
的優點在於代碼的惰性執行,Generator
所實現的事情,咱們不使用它也能夠作到,只是使用Generator
後,可以讓代碼的可讀性變得更好、流程變得更清晰、更專一於邏輯的實現。
若是有什麼不懂的地方 or 文章中一些的錯誤,歡迎指出