ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[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 作为协程的生成器(协同多任务处理)