iT邦幫忙

2022 iThome 鐵人賽

DAY 21
2

前言

這篇要來說明 Generator 產生器,不過在那之前要先來了解另一個也跟它有關的東西-Iterator 疊代器。


Iterator 疊代器

在 JS 中,有些型別是可以迭代的像是 Array/TypeArray、Set、Map、String、函數中的 arguments、NodeList。

而這些型別當中,從原型鏈往上查找,會找到 @@iterator 屬性,像陣列就有 Array.prototype[@@iterator](),透過呼叫 @@iterator 就可以迭代陣列。

範例

在此範例中,Symbol.iterator 會回傳 Iterator 物件,這個 Iterator 就是一個拜訪資料結構的 pointer,每次呼叫 next() 方法,就會回傳物件,裡面包括當前步驟返回的值 value 和是否完成的狀態 done。

const nums = [1, 2, 3];
const numsIterator = nums[Symbol.iterator]();

console.log(numsIterator.next()); // {value: 1, done: false}
console.log(numsIterator.next()); // {value: 2, done: false}
console.log(numsIterator.next()); // {value: 3, done: false}
console.log(numsIterator.next()); // {value: undefined, done: true}

Generator 產生器

有了 Iterator 的概念後,接著來看 Generator,它和一般的函式不同,一般的函式呼叫後就會持續執行直到結束,但 Generator 可以使用 yield 和 next() 暫停和繼續執行,幫使用者維護其內部的狀態

以下來看幾個範例:

範例1

在此範例中可以看到 names 函式加上了 *,這是用來告訴 JS 這是一個 Generator 函式,而函式內部使用了 yield 關鍵字來定義內部執行狀態。

在運作過程中,函式會以起始狀態 => 繼續(執行 next() 方法) => 暫停(遇到 yield 關鍵字) => 繼續 => 暫停...不斷反覆到結束(沒有輸出值時)。並且每次執行函式時會返回一個 Generator 物件(但本質是 Iterator 物件),如下方範例中看到的 console 值。

function* names() {
  yield 'Tom';
  yield 'Harry';
  yield 'Jerry';
}

const getName = names();
console.log(getName.next()); // {value: "Tom", done: false}
console.log(getName.next()); // {value: "Harry", done: false}
console.log(getName.next()); // {value: "Jerry", done: false}
console.log(getName.next()); // {value: undefined, done: true}

範例2

infinite loop,只要不 return 該函式,它就可以無止盡呼叫 next() 執行下去。

function* generateCount() {
  let count = 0;
  
  while (true) {
    const increment = yield count; // increment 為 next() 傳入的參數,透過 yield 返回
    increment != null ? count += increment : count++;
  }
}

const generatorObj = generateCount();
console.log(generatorObj.next()); // { done: false, value: 0 },這邊傳入參數無效,yield 還不會回傳
console.log(generatorObj.next()); // { done: false, value: 1 }
console.log(generatorObj.next(3)); // { done: false, value: 4 }
console.log(generatorObj.next()); // { done: false, value: 5 }
console.log(generatorObj.return()); // { done: false, value: undefined },離開 generator 函式
console.log(generatorObj.throw(new Error('Error!!'))); // 拋出錯誤

另外,除了 next() 外,Generator 還有 return() & throw() 可以結束函式和拋出錯誤。

Generator 使用案例

在了解 Generator 後,前面提到的都是 Generator 的一些範例程式,有沒有實際在專案或是函式庫、套件等地方使用的案例呢?

答案是有,上篇提到的 async/await 就是一種語法糖,背後的運作原理就是 Generator。

以下是來自 High-performance ES2015 and beyond 的一段話:

Async functions are essentially sugar on top of generators, so they fall into the same category.

除了這個例子之外,有名的 Redux middleware - redux-saga 也使用到了 Generator。

以下範例節錄自 redux-saga 官網教學:

import { put, takeEvery } from 'redux-saga/effects'

const delay = (ms) => new Promise(res => setTimeout(res, ms))

// ...

// Our worker Saga: will perform the async increment task
export function* incrementAsync() {
  yield delay(1000)
  yield put({ type: 'INCREMENT' })
}

// Our watcher Saga: spawn a new incrementAsync task on each INCREMENT_ASYNC
export function* watchIncrementAsync() {
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}

參考資料 & 推薦閱讀

JavaScript ES6 Generators 生成器

Learn JavaScript Generators In 12 Minutes

ES6 Generators in JavaScript, a Real-World Use Case


上一篇
Day20-非同步處理的方式-async/await
下一篇
Day22-了解 Memoization 機制
系列文
強化 JavaScript 之 - 程式語感是可以磨練成就的30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言