[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过时了吗?
- ==个人笔记==