之前我们在Symbol一章中介绍了iterator迭代器,今天主要来看看generator生成器。
回顾一下什么是迭代器:一种有序、连续的基于抽取的消耗数据的组织方式。
而对象是无序的,所以不存在迭代器,我们可以给object部署一个迭代器:
function iterator() {let array = Object.entries(this);let nextIndex = 0;return {next() {return nextIndex < array.length? { value: array[nextIndex++], done: false }: { value: undefined, done: true };},};}let obj = {a: 1,b: 2,c: 3,[Symbol.iterator]: iterator};for (const iterator of obj) {console.log(iterator);}// ['a', 1]// ['b', 2]// ['c', 3]
在JS中默认调用迭代器接口的场合有:
for...of...遍历...拓展运算符Array.from()Map和Set初始化数据Promise.all()和Promise.race()yield
生成器
生成器generator函数写法也是一个函数,只不过多了一个*,函数的返回值是一个迭代器对象
function* test() {}let iter = test();console.log(iter); // test {<suspended>}console.log(iter.next()); // {value: undefined, done: true}
generator函数要和yield关键词配合使用才能凸显出它的作用。yield简单理解就是产出的意思,每次yield就相当于给迭代器产出一个值,当函数运行的时候遇到yield会暂停函数的执行。
function* test() {yield "a";console.log(1); // 遇到 yield 会暂停函数的执行,所以这里不会执行yield "b";yield "c";return "d";}let iter = test();
因为generator返回的是迭代器对象,所以可以通过调用next方法来取得yield产出的值。
function* test() {yield "a";console.log(1);yield "b";yield "c";return "d";}let iter = test();console.log(iter.next()); // {value: 'a', done: false}console.log(iter.next()); // 1 (第二次调用 next 方法的时候才会打印 1)// {value: 'b', done: false}
yield的值可以被接收,但是接收的方式却让人费解!yield的值是通过next传递的参数来决定的,而不是yield本身。
function* foo() {let value1 = yield 1;console.log(value1); // twolet value2 = yield 2;console.log(value2); // threelet value3 = yield 3;console.log(value3); // fourlet value4 = yield 4;console.log(value4); // five}let iter = foo();console.log(iter.next("one")); // 这里的 one 没有用console.log(iter.next("two"));console.log(iter.next("three"));console.log(iter.next("four"));console.log(iter.next("five"));
这有点交叉传值的感觉:
yield和return的区别:
return依然会产出值,但是done为true,再次next的时候值为undefindeyield本意是产出暂停,具有记忆功能,继上次next后可以继续next


⚠️ 注意yield只能出现在生成器函数中,否则会抛异常
function test() {yield "a"; // Unexpected string}let iter = test();console.log(iter)
yield也可以当作表达式来使用:
function* demo() {yield; // yield 单独存在console.log("hello" + (yield 123)); // 表达式中使用需要使用括号}let iter = demo();console.log(iter.next());
// yield 当做参数传递的时候,无法获取值// 先执行两次 yield 然后才执行 foo()function foo(a, b) {console.log(a, b); // undefined undefined}function* demo() {foo(yield "a", yield "b");}let iter = demo();console.log(iter.next()); // {value: 'a', done: false}console.log(iter.next()); // {value: 'b', done: false}console.log(iter.next()); // {value: undefined, done: true}
因为generator返回的是迭代器对象,所以可以利用for...of...来遍历:
function* foo() {yield 1;yield 2;yield 3;yield 4;yield 5;return 6;}for (const iterator of foo()) {console.log(iterator); // 1 2 3 4 5 ,不包括 return 的 6}
到此我们对yield有了一个基本的认识,我们可以简化一下文章开头时模拟的object迭代器接口:
let obj = {start: [1, 2, 3, 4],end: [7, 8, 9, 10],[Symbol.iterator]: function* () {let nextIndex = 0;let array = this.start.concat(this.end);// 不需要再判断 return 值while (nextIndex < array.length) {yield array[nextIndex++];}},};for (const iterator of obj) {console.log(iterator);}
迭代器的案例
在异步概念之前假如我们想实现连续的读取文件需要一直使用回调,这样很容易造成回调地狱:
let fs = require("fs");fs.readFile("./name.txt", "utf-8", (err, data) => {fs.readFile(data, "utf-8", (err, data) => {fs.readFile(data, "utf-8", (err, data) => {console.log(data);});});});
当有了promise后我们可以将fs.readFile函数封装位异步操作:
function promisify(fn) {return function (...args) {return new Promise((resolve, reject) => {fn(...args, (err, data) => {if (err) reject(err);else resolve(data);});});};}let fs = require("fs");let readFile = promisify(fs.readFile);readFile("./name.txt", "utf-8").then((res) => readFile(res)).then((res) => console.log(res));
如何利用yield来封装呢?
let fs = require("fs");let readFile = promisify(fs.readFile);function promisify(fn) {return function (...args) {return new Promise((resolve, reject) => {fn(...args, (err, data) => {if (err) reject(err);else resolve(data);});});};}function* read() {let value1 = yield readFile("./name.txt", "utf-8");let value2 = yield readFile(value1, "utf-8");let value3 = yield readFile(value2, "utf-8");console.log(value3);}// 不如 .then 的写法简单let iter = read();let { value } = iter.next(); // 返回迭代器对象,将 value 进行解构value.then((result) => {let { value } = iter.next(result);value.then((result) => {let { value } = iter.next(result);value.then((result) => {console.log(result);});});});
但是这样的写法还不如promisify的写法简单,那我们继续优化:
let fs = require("fs");let readFile = promisify(fs.readFile);function promisify(fn) {return function (...args) {return new Promise((resolve, reject) => {fn(...args, (err, data) => {if (err) reject(err);else resolve(data);});});};}function* read() {let value1 = yield readFile("./name.txt", "utf-8");let value2 = yield readFile(value1, "utf-8");let value3 = yield readFile(value2, "utf-8");console.log(value3);}function Co(iter) {return new Promise((resolve, reject) => {let next = (data) => {let { value, done } = iter.next(data);if (done) {resolve(value);} else {value.then((res) => next(res));}};next();});}let promise = Co(read());promise.then((result) => {console.log(result);});
而这样的写法就是async、await的演变过程(*表示async,yield表示await)
