[TOC]
# 22. 生成器 Generators
示例代碼:[generator-examples](https://github.com/rauschma/generator-examples)
## 22.1 概述
### 22.1.1 什么是生成器?
你可以將生成器視為可以暫停和恢復的進程(一段代碼):
```js
function* genFunc() {
// (A)
console.log('First');
yield;
console.log('Second');
}
```
注意生成器的語法:`function*` 標志該函數為生成器函數(相對也有生成器方法);
`yield` 可以暫停生成器,此外通過它,生成器還能接受輸入和發送輸出。
調用一個生成器函數時,就會得到一個生成器對象`genObj`,通過該對象來實現進程控制(control the process:):
```js
const genObj = genFunc();
```
整個進程一開始處于暫停(位于A行),`genObj.next()`將恢復執行,然后`yield`將暫停執行:
```js
genObj.next();
// Output: First
genObj.next();
// output: Secon
```
### 22.1.2 生成器的種類
有四種生成器:
1. 生成器函數聲明:
```js
function* genFunc() {...}
const genObj = genFunc();
```
2. 生成器函數表達式:
```js
const genFunc = function* () {...};
const genObj = genFunc();
```
3. 對象字面量中生成器方法的定義:
```js
const obj = {
* generatorMethod(){
...
}
};
const genObj = obj.generatorMethod();
```
4. 類中的生成器方法的定義:
```js
class MyClass {
* generatorMethod(){
...
}
}
const myInst = new MyClass();
const genObj = myInst.generatorMethod();
```
### 22.1.3 案例:實現迭代
生成器返回的對象是可迭代的;每個`yield` 按需產生迭代值。因此我們可以使用生成器來實現可迭代值,可以被各種ES6語言機制使用:例如`for-of`循環、展開運算符(`…`)等。
下面的函數返回一個對象的屬性迭代值,每個屬性對應的`[key,value]`:
```js
function* objectEntries(obj){
const propKeys = Reflect.ownKeys(obj);
for(const propKey of propKeys){
// `yield` returns a value and then pauses
// the generator. Later, execution continues
// where it was previously paused.
yield [propKey, obj[propKey]];
}
}
```
然后使用 `objectEntries()`:
```js
const jane = { first: 'Jane', last: 'Doe' };
for (const [key,value] of objectEntries(jane)) {
console.log(`${key}: ${value}`);
}
// Output:
// first: Jane
// last: Doe
```
具體`objectEntries()` 如何工作的,在專門部分中有解釋。
實現同樣的功能,不使用生成器的話,需要花費更多的工作。
### 22.1.4 案例:更簡單的異步代碼
使用生成器可以很大程度上簡化了`Promises`的工作。
下面看一下基于`Promise`的`fetchJson()`怎么通過生成器來進行改善:
```js
function fetchJson(url){
return fetch(url)
.then(request => request.text())
.then(text => {
return JSON.parse(text);
})
.catch(error => {
console.log(`ERROR:${error.stack}`);
});
}
```
通過[co](https://github.com/tj/co)庫和生成器,使得異步代碼看起來是同步的:
```js
const fetchJson = co.wrap(function* (url) {
try {
let request = yield fetch(url);
let text = yield request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
```
ECMAScript 2017 發布的 `async`,內部是基于生成器的。可以像下面這樣使用:
```js
async function fetchJson(url){
try {
let request = await fetch(url);
let text = await request.text();
return JSON.parse(text);
}
catch (error) {
console.log(`ERROR: ${error.stack}`);
}
}
```
使用時,上面的所有版本代碼都可以如下調用:
```js
fetchJson('http://example.com/some_file.json')
.then(obj => console.log(obj));
```
### 22.1.5 案例:接收異步數據:
生成器可以通過`yield` 從 `next()` 接收輸入。這意味著只要新數據異步到達生成器就可以喚醒生成器,感覺就像它同步接收數據一樣。
## 什么是生成器
生成器是可以暫停和恢復的函數(可以考慮協同多任務處理(cooperative multitasking)或協同程序(coroutines)),它支持各種應用程序。
首先,看如下名為`genFunc`的生成器函數:
```js
function* genFunc() {
// (A)
console.log('First');
yield; // (B)
console.log('Second'); // (C)
}
```
可以看出,它與普通函數的兩點區別:
*. 使用了`function*` 進行聲明。
*. 可以通過 `yield`,暫停自身的執行(行B)。
調用 `genFunc` 不會執行代碼主體,會得到一個 生成器對象,用來對執行主體的控制。
```js
const genObj = genFunc();
```
`genFunc()` 一開始會在代碼主體執行之前暫停(行A)。調用 `genObj.next()`會執行到下一個`yield`位置:
```js
> genObj.next()
First
{ value: undefined, done: false }
```
可以看到`genObj.next()`返回了一個對象。接著看下面...
`genFunc` 現在在行B處暫停。如果在調用`next()`,執行進程恢復,行C被執行:
```js
> genObj.next()
Second
{ value: undefined, done: true }
```
之后,該函數執行完畢,離開函數體;接下來再執行`genObj.next()` 是沒有任何效果的。
### 22.2.1 生成器的用處
在三個方面扮演重要作用:
1. 迭代器(數據生產者):
每個`yield` 都可以通過next()返回一個值,這意味著生成器可以通過循環和遞歸生成值序列。由于生成器對象實現了Iterable接口(在[迭代章節](http://exploringjs.com/es6/ch_iteration.html#ch_iteration)中有解釋),這些序列可以由任何支持iterables的ECMAScript 6構造處理。兩個例子是:`for-of`循環和擴展運算符(`...`)。
2. 觀察者(數據消費者):
`yield` 也可以從 `next()`(通過參數)接收值。這意味著生成器會成為數據使用者,在通過`next()`向它們推送新值之前會暫停。
4. 協同程序(數據生產者和消費者):
考慮到生成器是可暫停的,可以同時是數據生產者和數據消費者,將它們轉換為協同程序(協作的多任務任務) (coroutines (cooperatively multitasked tasks))不需要做太多工作。
接下來會對這些角色進行更深入的解釋。
## 22.3 作為迭代器的生成器(數據生成)
可以查看[上一章 迭代器](http://exploringjs.com/es6/ch_iteration.html#ch_iteration)的相關知識。
## 22.5 作為協程的生成器(協同多任務處理)
- 關于本書
- 目錄簡介
- 關于這本書你需要知道的
- 序
- 前言
- I 背景
- 1. About ECMAScript 6 (ES6)
- 2. 常見問題:ECMAScript 6
- 3. 一個JavaScript:在 ECMAScript 6 中避免版本化
- 4. 核心ES6特性
- II 數據
- 5. New number and Math features
- 6. 新的字符串特性
- 7. Symbol
- 8. Template literals
- 第9章 變量與作用域
- 第10章 解構
- 第11章 參數處理
- III 模塊化
- 12. ECMAScript 6中的可調用實體
- 13. 箭頭函數
- 14. 除了類之外的新OOP特性
- 15. 類
- 16. 模塊
- IV 集合
- 17. The for-of loop
- 18. New Array features
- 19. Maps and Sets
- 20. 類型化數組
- 21. 可迭代對象和迭代器
- 22. 生成器( Generator )
- V 標準庫
- 23. 新的正則表達式特性
- 24. 異步編程 (基礎知識)
- 25. 異步編程的Promise
- VI 雜項
- 26. Unicode in ES6
- 27. 尾部調用優化
- 28 用 Proxy 實現元編程
- 29. Coding style tips for ECMAScript 6
- 30. 概述ES6中的新內容
- 注釋
- ES5過時了嗎?
- ==個人筆記==