Generator的正確打開方式

前兩年大量的在寫Generator+co,用它來寫一些相似同步的代碼
但實際上,Generator並非被造出來幹這個使的,否則也就不會有後來的asyncawait
Generator是一個能夠被暫停的函數,而且什麼時候恢復,由調用方決定
但願本文能夠幫助你理解Generator到底是什麼,以及怎麼用javascript

放一張圖來表示我對Generator的理解:前端

一個咖啡機,雖然說我並不喝咖啡,惋惜找不到造王老吉的機器-.-

我所理解的Generator咖啡機大概就是這麼的一個樣子的:java

  1. 首先,咱們往機器裏邊放一些咖啡豆
  2. 等咱們想喝咖啡的時候,就能夠按開關(gen.next()),機器開始磨咖啡豆、煮咖啡、接下來就獲得咖啡了
  3. 等接滿了一杯咖啡後,閥門就會自動關閉(yield)
  4. 若是你一開始往機器裏邊放的咖啡豆不少的話,此時,機器裏邊仍是會有一些剩餘的,下次再想喝還能夠繼續按開關,執行(磨豆、煮咖啡、接咖啡)這一套操做

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)
複製代碼

代碼運行後,咱們首先會獲得一條cookinglog
而後在3s後會再次獲得一條loges6

這就解釋了Generator是什麼:
一個能夠暫停的迭代器
調用next來獲取數據(咱們本身來決定是否什麼時候煮咖啡
在遇到yield之後函數的執行就會中止(接滿了一杯,閥門關閉
咱們來決定什麼時候運行剩餘的代碼next何時想喝了再去煮github

這是Generator中最重要的特性,咱們只有在真正須要的時候才獲取下一個值,而不是一次性獲取全部的值ajax

Generator的語法

聲明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函數經過調用兩次方法,將會生成兩個徹底獨立的狀態機
因此,保存當前的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}
複製代碼

Method: next()

最經常使用的next()方法,不管什麼時候調用它,都會獲得下一次輸出的返回對象(在代碼執行完後的調用將會始終返回{value: undefined, done: true})。

next總會返回一個對象,包含兩個屬性值:
valueyield關鍵字後邊表達式的值
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對象將不會再返回任何有效的值

Method: return()

咱們能夠在迭代器對象上直接調用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}
複製代碼

Method: throw()

在調用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的語法

yield的語法有點像return,可是,return是在函數調用結束後返回結果的
而且在調用return以後不會執行其餘任何的操做

function method (a) {
  let b = 5
  return a + b
  // 下邊的兩句代碼永遠不會執行
  b = 6
  return a * b
}

method(6) // 11
method(6) // 11
複製代碼

而yield的表現則不同

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*

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的返回值

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的結合

再次重申,我我的不認爲async/await是Generator的語法糖。。

若是是寫前端的童鞋,基本上都會遇處處理分頁加載數據的時候
若是結合着Generator+asyncawait,咱們能夠這樣實現:

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 文章中一些的錯誤,歡迎指出

參考資料

  1. Javascript (ES6) Generators — Part I: Understanding Generators
  2. What are JavaScript Generators and how to use them

文章示例代碼

相關文章
相關標籤/搜索