之前我们在Symbol一章中介绍了iterator迭代器,今天主要来看看generator生成器。
回顾一下什么是迭代器:一种有序、连续的基于抽取的消耗数据的组织方式。

而对象是无序的,所以不存在迭代器,我们可以给object部署一个迭代器:

  1. function iterator() {
  2. let array = Object.entries(this);
  3. let nextIndex = 0;
  4. return {
  5. next() {
  6. return nextIndex < array.length
  7. ? { value: array[nextIndex++], done: false }
  8. : { value: undefined, done: true };
  9. },
  10. };
  11. }
  12. let obj = {
  13. a: 1,
  14. b: 2,
  15. c: 3,
  16. [Symbol.iterator]: iterator
  17. };
  18. for (const iterator of obj) {
  19. console.log(iterator);
  20. }
  21. // ['a', 1]
  22. // ['b', 2]
  23. // ['c', 3]

JS中默认调用迭代器接口的场合有:

  1. for...of...遍历
  2. ...拓展运算符
  3. Array.from()
  4. MapSet初始化数据
  5. Promise.all()Promise.race()
  6. yield

生成器

生成器generator函数写法也是一个函数,只不过多了一个*,函数的返回值是一个迭代器对象

  1. function* test() {}
  2. let iter = test();
  3. console.log(iter); // test {<suspended>}
  4. console.log(iter.next()); // {value: undefined, done: true}

generator函数要和yield关键词配合使用才能凸显出它的作用。
yield简单理解就是产出的意思,每次yield就相当于给迭代器产出一个值,当函数运行的时候遇到yield会暂停函数的执行。

  1. function* test() {
  2. yield "a";
  3. console.log(1); // 遇到 yield 会暂停函数的执行,所以这里不会执行
  4. yield "b";
  5. yield "c";
  6. return "d";
  7. }
  8. let iter = test();

因为generator返回的是迭代器对象,所以可以通过调用next方法来取得yield产出的值。

  1. function* test() {
  2. yield "a";
  3. console.log(1);
  4. yield "b";
  5. yield "c";
  6. return "d";
  7. }
  8. let iter = test();
  9. console.log(iter.next()); // {value: 'a', done: false}
  10. console.log(iter.next()); // 1 (第二次调用 next 方法的时候才会打印 1)
  11. // {value: 'b', done: false}

yield的值可以被接收,但是接收的方式却让人费解!yield的值是通过next传递的参数来决定的,而不是yield本身。

  1. function* foo() {
  2. let value1 = yield 1;
  3. console.log(value1); // two
  4. let value2 = yield 2;
  5. console.log(value2); // three
  6. let value3 = yield 3;
  7. console.log(value3); // four
  8. let value4 = yield 4;
  9. console.log(value4); // five
  10. }
  11. let iter = foo();
  12. console.log(iter.next("one")); // 这里的 one 没有用
  13. console.log(iter.next("two"));
  14. console.log(iter.next("three"));
  15. console.log(iter.next("four"));
  16. console.log(iter.next("five"));

这有点交叉传值的感觉:
WechatIMG521.png

yieldreturn的区别:

  1. return依然会产出值,但是donetrue,再次next的时候值为undefinde
  2. yield本意是产出暂停,具有记忆功能,继上次next后可以继续next

image.png
image.png

⚠️ 注意
yield只能出现在生成器函数中,否则会抛异常

  1. function test() {
  2. yield "a"; // Unexpected string
  3. }
  4. let iter = test();
  5. console.log(iter)

yield也可以当作表达式来使用:

  1. function* demo() {
  2. yield; // yield 单独存在
  3. console.log("hello" + (yield 123)); // 表达式中使用需要使用括号
  4. }
  5. let iter = demo();
  6. console.log(iter.next());
  1. // yield 当做参数传递的时候,无法获取值
  2. // 先执行两次 yield 然后才执行 foo()
  3. function foo(a, b) {
  4. console.log(a, b); // undefined undefined
  5. }
  6. function* demo() {
  7. foo(yield "a", yield "b");
  8. }
  9. let iter = demo();
  10. console.log(iter.next()); // {value: 'a', done: false}
  11. console.log(iter.next()); // {value: 'b', done: false}
  12. console.log(iter.next()); // {value: undefined, done: true}

因为generator返回的是迭代器对象,所以可以利用for...of...来遍历:

  1. function* foo() {
  2. yield 1;
  3. yield 2;
  4. yield 3;
  5. yield 4;
  6. yield 5;
  7. return 6;
  8. }
  9. for (const iterator of foo()) {
  10. console.log(iterator); // 1 2 3 4 5 ,不包括 return 的 6
  11. }

到此我们对yield有了一个基本的认识,我们可以简化一下文章开头时模拟的object迭代器接口:

  1. let obj = {
  2. start: [1, 2, 3, 4],
  3. end: [7, 8, 9, 10],
  4. [Symbol.iterator]: function* () {
  5. let nextIndex = 0;
  6. let array = this.start.concat(this.end);
  7. // 不需要再判断 return 值
  8. while (nextIndex < array.length) {
  9. yield array[nextIndex++];
  10. }
  11. },
  12. };
  13. for (const iterator of obj) {
  14. console.log(iterator);
  15. }

迭代器的案例

在异步概念之前假如我们想实现连续的读取文件需要一直使用回调,这样很容易造成回调地狱:

  1. let fs = require("fs");
  2. fs.readFile("./name.txt", "utf-8", (err, data) => {
  3. fs.readFile(data, "utf-8", (err, data) => {
  4. fs.readFile(data, "utf-8", (err, data) => {
  5. console.log(data);
  6. });
  7. });
  8. });

当有了promise后我们可以将fs.readFile函数封装位异步操作:

  1. function promisify(fn) {
  2. return function (...args) {
  3. return new Promise((resolve, reject) => {
  4. fn(...args, (err, data) => {
  5. if (err) reject(err);
  6. else resolve(data);
  7. });
  8. });
  9. };
  10. }
  11. let fs = require("fs");
  12. let readFile = promisify(fs.readFile);
  13. readFile("./name.txt", "utf-8")
  14. .then((res) => readFile(res))
  15. .then((res) => console.log(res));

如何利用yield来封装呢?

  1. let fs = require("fs");
  2. let readFile = promisify(fs.readFile);
  3. function promisify(fn) {
  4. return function (...args) {
  5. return new Promise((resolve, reject) => {
  6. fn(...args, (err, data) => {
  7. if (err) reject(err);
  8. else resolve(data);
  9. });
  10. });
  11. };
  12. }
  13. function* read() {
  14. let value1 = yield readFile("./name.txt", "utf-8");
  15. let value2 = yield readFile(value1, "utf-8");
  16. let value3 = yield readFile(value2, "utf-8");
  17. console.log(value3);
  18. }
  19. // 不如 .then 的写法简单
  20. let iter = read();
  21. let { value } = iter.next(); // 返回迭代器对象,将 value 进行解构
  22. value.then((result) => {
  23. let { value } = iter.next(result);
  24. value.then((result) => {
  25. let { value } = iter.next(result);
  26. value.then((result) => {
  27. console.log(result);
  28. });
  29. });
  30. });

但是这样的写法还不如promisify的写法简单,那我们继续优化:

  1. let fs = require("fs");
  2. let readFile = promisify(fs.readFile);
  3. function promisify(fn) {
  4. return function (...args) {
  5. return new Promise((resolve, reject) => {
  6. fn(...args, (err, data) => {
  7. if (err) reject(err);
  8. else resolve(data);
  9. });
  10. });
  11. };
  12. }
  13. function* read() {
  14. let value1 = yield readFile("./name.txt", "utf-8");
  15. let value2 = yield readFile(value1, "utf-8");
  16. let value3 = yield readFile(value2, "utf-8");
  17. console.log(value3);
  18. }
  19. function Co(iter) {
  20. return new Promise((resolve, reject) => {
  21. let next = (data) => {
  22. let { value, done } = iter.next(data);
  23. if (done) {
  24. resolve(value);
  25. } else {
  26. value.then((res) => next(res));
  27. }
  28. };
  29. next();
  30. });
  31. }
  32. let promise = Co(read());
  33. promise.then((result) => {
  34. console.log(result);
  35. });

而这样的写法就是asyncawait的演变过程(*表示asyncyield表示await