18. 迭代器、生成器 - 图1

迭代器

什么是迭代器?

迭代器(iterator),是确使用户可在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节。它提供了一种获取一系列值(无论是有限还是无限个)的标准方式。
其行为像数据库中的光标,迭代器最早出现在1974年设计的CLU编程语言中;
在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如Java、Python等;

说人话就是:迭代器是一个工具对象,可以帮助我们对某个数据结构进行遍历。

JavaScript 中的迭代器

在JavaScript中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议规范(iterator protocol):
迭代器要求有一个获取值的标准方式,在 js 中这个标准方式就是迭代器对象中一个特定的 next 方法。

next 方法有如下的要求:

  • 是一个无参数或者一个参数的函数,返回一个应当拥有以下两个属性的对象:
  1. done(boolean)

如果迭代器可以产生序列中的下一个值,则为 false。(这等价于没有指定 done 这个属性。)
如果迭代器已将序列迭代完毕,则为 true。这种情况下,value 是可选的,如果它依然存在,即为迭代结束之后的默认返回值。

  1. value

迭代器返回的任何 JavaScript 值。done 为 true 时可省略。

说人话就是:next 函数一个一个遍历容器,并返回一个对象。对象中有两个属性,done:表示该容器中是否还有值,如果没有值了为 true,表示遍历可以结束了;value:表示当前遍历出来的值,如果容器中没有值了,value 就是 undefined,此时 done 就是 true。

以一个数组为例:

  1. // 待遍历的数组
  2. const arr = ['nba', 'cba', 'mba']
  3. // 数组迭代器迭代上面的数组
  4. let index = 0
  5. const iteratorArr = {
  6. next: function () {
  7. if (index < arr.length) {
  8. return { done: false, value: arr[index++] }
  9. } else {
  10. return { done: true, value: undefined }
  11. }
  12. }
  13. }
  14. console.log(iteratorArr.next());
  15. console.log(iteratorArr.next());
  16. console.log(iteratorArr.next());
  17. console.log(iteratorArr.next());
  18. console.log(iteratorArr.next());
  19. // { done: false, value: 'nba' }
  20. // { done: false, value: 'cba' }
  21. // { done: false, value: 'mba' }
  22. // { done: true, value: undefined }
  23. // { done: true, value: undefined }

什么是可迭代对象?

它和迭代器是不同的概念;
当一个对象实现了iterable protocol 可迭代协议时,它就是一个可迭代对象;
这个对象要求必须实现迭代器方法,并且要求使用 Symbol.iterator 作为迭代器方法的 key;

什么是可迭代协议?
可迭代协议就像一个接口,要想能被迭代,就得实现这个接口,也就是得按接口的规矩办事。很多容器就实现了这个接口,所以我们可以遍历出容器中的元素。换句话说,很多容器对象都是可迭代对象,并且是否实现可迭代协议,也是一个容器能否被遍历取值的必要条件。(难怪 Java 中这些集合都实现了 iterator 接口)

一个可迭代容器示例:

  1. // 一个写死的容器(可迭代对象)
  2. const iterableObj = {
  3. // 假设容器中已经存了这些数据
  4. arr: ["abc", "cba", "nba"],
  5. // 迭代器方法也是写死的,只能遍历上面的测试数据
  6. [Symbol.iterator]: function() {
  7. let index = 0
  8. return {
  9. next: () => { // 小细节:迭代器对象中要想访问到测试数据数组,得用箭头函数,
  10. if (index < this.arr.length) { // 因为 this 可以跳过迭代器对象作用域指向容器
  11. return { done: false, value: this.arr[index++] }
  12. } else {
  13. return { done: true, value: undefined }
  14. }
  15. }
  16. }
  17. }
  18. }
  19. // 获取该容器的迭代器
  20. const iteratorArr = iterableObj[Symbol.iterator]()
  21. console.log(iteratorArr.next());
  22. console.log(iteratorArr.next());
  23. console.log(iteratorArr.next());
  24. console.log(iteratorArr.next());
  25. // { done: false, value: 'abc' }
  26. // { done: false, value: 'cba' }
  27. // { done: false, value: 'nba' }
  28. // { done: true, value: undefined }

当一个容器实现可迭代协议成为一个可迭代容器后,我们就可以利用这个容器可被迭代的能力,添加一些语法糖,来帮助使用容器的人更好的遍历容器取值。
比如 for … of 就是一个方便我们对可迭代对象进行迭代的语法糖。

  1. // 一个写死的容器(可迭代对象)
  2. const iterableObj = {
  3. // 假设容器中已经存了这些数据
  4. arr: ["abc", "cba", "nba"],
  5. // 迭代器方法也是写死的,只能遍历上面的测试数据
  6. [Symbol.iterator]: function() {
  7. let index = 0
  8. return {
  9. next: () => { // 小细节:迭代器对象中要想访问到测试数据数组,得用箭头函数,
  10. if (index < this.arr.length) { // 因为 this 可以跳过迭代器对象作用域指向容器
  11. return { done: false, value: this.arr[index++] }
  12. } else {
  13. return { done: true, value: undefined }
  14. }
  15. }
  16. }
  17. }
  18. }
  19. for (const item of iterableObj) {
  20. console.log(item);
  21. }
  22. // 很方便的取出了值,而不用我们去 value 里面扣,这个糖真不错
  23. // abc
  24. // cba
  25. // nba

原生迭代器对象

JavaScript 中原生的容器都实现了可迭代协议,所以它们都是可迭代的,可迭代对象就是这些容器的实例。

  • String、Array、Map、Set、arguments对象、NodeList集合 ```javascript // String for (const iterator of “hello”) { console.log(iterator); // h e l l o }

// array const arr = [‘abc’, ‘cba’, ‘nba’] for (const iterator of arr) { console.log(iterator); // abc cba nba }

// Map const map = new Map() map.set(‘key1’, ‘a’) map.set(‘key2’, ‘b’) map.set(‘key3’, ‘c’) for (const iterator of map) { console.log(iterator) // [ ‘key1’, ‘a’ ] [ ‘key2’, ‘b’ ] [ ‘key3’, ‘c’ ] }

// Set const set = new Set() set.add(‘a’) set.add(‘b’) set.add(‘c’) for (const iterator of set) { console.log(iterator); // a b c }

// 函数中arguments也是一个可迭代对象 function foo(x, y, z) { for (const arg of arguments) { console.log(arg) } } foo(10, 20, 30) // 10 20 30

  1. <a name="Qp6eW"></a>
  2. ## 可迭代对象的应用体现
  3. 可迭代对象的应用体现在很多方面:
  4. - JavaScript中语法:for ...of、展开语法(spread syntax)、yield*(后面讲)、解构赋值(Destructuring_assignment);
  5. - 创建一些对象时:new Map([Iterable])、new WeakMap([iterable])、new Set([iterable])、new WeakSet([iterable]);
  6. - 一些方法的调用:Promise.all(iterable)、Promise.race(iterable)、Array.from(iterable);
  7. ```javascript
  8. // 1.for of场景
  9. // 2.展开语法(spread syntax)
  10. const iterableObj = {
  11. names: ["abc", "cba", "nba"],
  12. [Symbol.iterator]: function() {
  13. let index = 0
  14. return {
  15. next: () => {
  16. if (index < this.names.length) {
  17. return { done: false, value: this.names[index++] }
  18. } else {
  19. return { done: true, value: undefined }
  20. }
  21. }
  22. }
  23. }
  24. }
  25. const arr = [1, 2, 3]
  26. const newNames = [...arr, ...iterableObj]
  27. console.log(newNames) // [ 1, 2, 3, 'abc', 'cba', 'nba' ]
  28. // 普通对象为什么也能用展开语法?
  29. const obj = { name: 'zs', age: 20}
  30. const objarr = {...obj}
  31. console.log(objarr); // { name: 'zs', age: 20 }
  32. // 因为这是 ES9(ES2018) 作了特殊处理,新增的一个特性: 内部实现用的不是迭代器。
  33. // ES9 之前是不能用展开语法的
  34. // 3.解构语法
  35. const [ name1, name2 ] = arr
  36. // 普通对象也能解构,也是 ES9 特意的处理,让解构支持普通对象,内部实现也不是迭代器
  37. const { name, age } = obj
  38. console.log(name, age); // zs 20
  39. // 4.创建一些其他对象时,它会要求你传入一个可迭代对象,要不然报错
  40. // 这时我们就能知道什么能传什么不能传,更方便的阅读 API
  41. const set1 = new Set(iterableObj) // 自定义的容器实例
  42. const set2 = new Set(arr) // 数组实例
  43. const set3 = new Set('asdf') // String 实例
  44. // 数字就不是可迭代对象,它的构造函数中没有迭代器
  45. // const set4 = new Set(123) // number 123 is not iterable (cannot read property Symbol(Symbol.iterator))
  46. // 5.Promise.all
  47. // 开始我们只是传 promise 的数组,其实只要可迭代对象都行
  48. Promise.all(iterableObj).then(res => {
  49. console.log(res)
  50. })
  51. // 相当于:Promise.all( Promise.resolve(iterableObj) ).then()

手动实现一个简易可迭代容器

上面已经实现了一个写死的可迭代容器,我们可以稍微完善一下。

  1. 要想可迭代,这个容器就得实现可迭代协议。
  2. 容器中数据的存储结构,就用一个数组,并且存储结构挂载在容器属性上。
  3. 一些容器基本的方法,如添加方法,精确查找方法,删除方法就也免了。实例化的时候就添加数据。 ```javascript // 创建一个教室容器类, 创建出来的对象都是可迭代教室对象 class Classroom { constructor(address, name, students) { this.address = address this.name = name this.students = students }

    Symbol.iterator { let index = 0 return { next: () => {

    1. if (index < this.students.length) {
    2. return { done: false, value: this.students[index++] }
    3. } else {
    4. return { done: true, value: undefined }
    5. }

    } } } }

const classroom = new Classroom(“3幢5楼205”, “计算机教室”, [“james”, “kobe”, “curry”])

// 迭代成功 for (const item of classroom) { console.log(item); // james kobe curry }

  1. <a name="eclxI"></a>
  2. ## 迭代器的终止
  3. 迭代器一般情况下会迭代完所有元素,但也可再某些条件下中断:
  4. - 比如遍历的过程中通过 break、continue、return、throw 中断了循环操作;
  5. - 比如在解构的时候,没有解构所有的值;
  6. 那么这个时候我们想要监听中断的话,可以在迭代器中添加 return 方法:
  7. ```javascript
  8. // 创建一个教室容器类, 创建出来的对象都是可迭代教室对象
  9. class Classroom {
  10. constructor(address, name, students) {
  11. this.address = address
  12. this.name = name
  13. this.students = students
  14. }
  15. [Symbol.iterator]() {
  16. let index = 0
  17. return { // 迭代器
  18. next: () => {
  19. if (index < this.students.length) {
  20. return { done: false, value: this.students[index++] }
  21. } else {
  22. return { done: true, value: undefined }
  23. }
  24. },
  25. // 迭代中断后的处理
  26. return: () => {
  27. console.log("迭代器提前终止了~")
  28. // 也需要返回一个对象,包含这两个属性,done 为 true
  29. return { done: true, value: undefined }
  30. }
  31. }
  32. }
  33. }
  34. const classroom = new Classroom("3幢5楼205", "计算机教室", ["james", "kobe", "curry" ])
  35. for (const item of classroom) {
  36. console.log(item); // james kobe
  37. if(item == 'kobe') break; // 迭代器提前终止了~
  38. }

生成器

什么是生成器?

生成器是ES6中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执
行等。
平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常。

生成器函数也是一个函数,但是和普通的函数有一些区别:

  • 首先,生成器函数需要在function的后面加一个符号:*
  • 其次,生成器函数可以通过 yield 关键字来控制函数的执行流程:
  • 最后,生成器函数的返回值是一个 Generator(生成器)

    生成器函数执行 yield

    直接调用生成器函数是不会执行的,它只是返回了一个生成器对象,需要通过生成器对象的 next 方法才能执行函数中的代码。
    执行到 yield 会中断函数执行,再次调用 next 方法就会继续往下执行。生成器事实上是一种特殊的迭代器,yield 好像把函数切成了几段。 ```javascript function* foo() { console.log(“函数开始执行~”)

    const value1 = 100 console.log(“第一段代码:”, value1) // 中断函数执行 yield

    const value2 = 200 console.log(“第二段代码:”, value2) yield

    const value3 = 300 console.log(“第三段代码:”, value3) yield

    console.log(“函数执行结束~”) }

// 直接调用生成器函数是不会执行的 foo() // 调用生成器函数时, 会给我们返回一个生成器对象 const generator = foo() // 需要通过生成器对象的 next 方法执行,执行到 yield 会中断函数执行 generator.next() // 需要再次调用 next 方法 console.log(“——————-“) generator.next()

// 函数开始执行~
// 第一段代码: 100 // ——————-
// 第二段代码: 200 // generator.next() // console.log(“—————“) // generator.next()

  1. 生成器作为特殊的迭代器,next 函数也会返回一个拥有 done value 属性的对象。
  2. **事实上生成器函数中 yield 就是一个特殊的 return 。**<br />next 函数中 return 会返回 done value 属性的对象(没写 return 语句,就是默认 return undefined),其中 return 后面的值,就是 value 的值;done 的值为 true,表示函数迭代完了,也就是终止函数。
  3. 结合前面终止迭代器的 return 函数,生成器中也有,它就相当于在 next 中添加了一个 return
  4. ```javascript
  5. function* foo1() {
  6. return ;
  7. }
  8. console.log(foo().next()) // { value: undefined, done: true }
  9. function* foo2() {
  10. return 123;
  11. }
  12. console.log(foo().next()) // { value: 123, done: true }
  13. function* foo3() {
  14. }
  15. console.log(foo().return(123)) // { value: 123, done: true }

而 yield 也会返回拥有 done 和 value 属性的对象,yield 后面的值,也会成为 value 的值。最大的区别是 done,yield 的 done 为 false,表示没迭代完,所以 yield 是中断函数执行,还可以恢复。

  1. function* foo() {
  2. console.log("函数开始执行~")
  3. const value1 = 100
  4. console.log("第一段代码:", value1)
  5. // 中断函数执行
  6. yield
  7. const value2 = 200
  8. console.log("第二段代码:", value2)
  9. yield 123
  10. console.log("函数执行结束~")
  11. }
  12. const generator = foo()
  13. console.log(generator.next())
  14. // 函数开始执行~
  15. // 第一段代码: 100
  16. // { value: undefined, done: false }
  17. console.log(generator.next())
  18. // 第二段代码: 200
  19. // { value: 123, done: false }

生成器传递参数 - next 函数

函数既然可以暂停来分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?
答案是可以的;我们在调用 next 函数的时候,可以给它传递参数, 并且这个值会作为上一个 yield 语句的返回值,这样 next 本次要执行的代码块就能拿到这个传进来的值了。

也就是说 next 函数能为本次执行的函数代码块传参。

  1. function* foo(num) {
  2. console.log("函数开始执行~")
  3. const value1 = 100
  4. console.log("第一段代码:", value1)
  5. // 中断函数执行
  6. const n = yield // next 的参数作为上一个 yield 的返回值
  7. const value2 = 200 + num + n // num 是整个函数的参数,n 是第二个 next 函数传进来的参数
  8. console.log("第二段代码:", value2)
  9. console.log("函数执行结束~")
  10. }
  11. const generator = foo(1)
  12. generator.next()
  13. generator.next(10)
  14. // 函数开始执行~
  15. // 第一段代码: 100
  16. // 第二段代码: 211
  17. // 函数执行结束~

生成器抛出异常 – throw函数

除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常,终止函数运行。它针对的是 yield 语句,所以捕获它的异常就能继续运行下去。catch 中也继续添加 yield 中断。

  1. function* foo(num) {
  2. console.log("函数开始执行~")
  3. const value1 = 100
  4. console.log("第一段代码:", value1)
  5. // 发生异常终止函数运行
  6. const n = yield
  7. // 捕获异常
  8. // const n = null
  9. // try {
  10. // n = yield
  11. // } catch (error) {
  12. // console.log('异常捕获');
  13. // }
  14. const value2 = 200 + num + n
  15. console.log("第二段代码:", value2)
  16. console.log("函数执行结束~")
  17. }
  18. const generator = foo(1)
  19. generator.next()
  20. generator.throw('抛出异常')
  21. generator.next(10)
  22. // 函数开始执行~
  23. // 第一段代码: 100
  24. // const n = yield
  25. // ^
  26. // 抛出异常

生成器替代迭代器

我们发现生成器是一种特殊的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:
迭代器的 next 方法会返回一个对象{done: , value: }yield也会返回这样一个对象。
yield 就是迭代器的语法糖一样,它可以在生成器函数中像迭代器一样以上面对象的形式将值返回。

  • 注意:yield 需要在生成器函数中使用,函数需要加* ```javascript function* generatorFn() { for (let i = 0; i < 3; i++) { yield i } } const generator = generatorFn() console.log(generator.next()); console.log(generator.next()); console.log(generator.next());

// { value: 0, done: false } // { value: 1, done: false } // { value: 2, done: false }

  1. ```javascript
  2. // 一个写死的容器(可迭代对象)
  3. const iterableObj = {
  4. arr: ["abc", "cba", "nba"],
  5. // 手动编写的迭代器函数,可以返回一个迭代器对象
  6. [Symbol.iterator]: function() {
  7. let index = 0
  8. return {
  9. next: () => {
  10. if (index < this.arr.length) {
  11. return { done: false, value: this.arr[index++] }
  12. } else {
  13. return { done: true, value: undefined }
  14. }
  15. }
  16. }
  17. }
  18. }
  19. console.log(iterableObj[Symbol.iterator]().next()); // { done: false, value: 'abc' }
  20. // 可以利用 yield 代替手动实现迭代器
  21. // yield 像 return 一样返回值,它取出可迭代对象中的值,并按迭代器返回对象的格式返回
  22. const iterableObj1 = {
  23. arr: ["abc", "cba", "nba"],
  24. // 改成生成器函数,并用 yield 代替 return 和 迭代器对象
  25. [Symbol.iterator]: function*() {
  26. let index = 0
  27. yield this.arr[index++]
  28. }
  29. }
  30. console.log(iterableObj1[Symbol.iterator]().next()); // { value: 'abc', done: false }

yield*

yield* 相当于 yield 的语法糖,语法糖的语法糖了属于是yield* 可以直接迭代可迭代对象,手动取值都不需要了。

  1. const iterableObj2 = {
  2. arr: ["abc", "cba", "nba"],
  3. // 改成生成器函数,并用 yield* 直接迭代可迭代对象
  4. [Symbol.iterator]: function*() {
  5. yield* this.arr
  6. }
  7. }
  8. console.log(iterableObj2[Symbol.iterator]().next()); // { value: 'abc', done: false }

自定义容器中也可以使用 yield* 来简化代码

  1. // 创建一个教室容器类, 创建出来的对象都是可迭代教室对象
  2. class Classroom {
  3. constructor(address, name, students) {
  4. this.address = address
  5. this.name = name
  6. this.students = students
  7. }
  8. // 注意:将函数改成生成器函数如果使用了键值对的语法糖, * 号加在最前面
  9. *[Symbol.iterator]() {
  10. yield* this.students
  11. }
  12. // 或者写成函数表达式的形式再加 * 号
  13. // [Symbol.iterator] = function* () {
  14. // yield* this.students
  15. // }
  16. }
  17. const classroom = new Classroom("3幢5楼205", "计算机教室", ["james", "kobe", "curry"])
  18. console.log(classroom[Symbol.iterator]().next()); // { value: 'james', done: false }

异步处理方案

学完了我们前面的Promise、生成器等,我们目前来看一下异步代码的最终处理方案。
需求:

  • 我们需要向服务器发送网络请求获取数据,一共需要发送三次请求;
  • 第二次的请求url依赖于第一次的结果;
  • 第三次的请求url依赖于第二次的结果;
  • 依次类推;

也就是说,我们要请求中发送请求,并且下一次的请求依赖上一次请求的结果。

最原始的方案:在请求成功后的 then 函数中套娃再次调用请求函数发送请求。但发生了回调地狱

  1. // request.js
  2. function requestData(url) {
  3. // 异步请求的代码会被放入到executor中
  4. return new Promise((resolve, reject) => {
  5. // 模拟网络请求
  6. setTimeout(() => {
  7. // 拿到请求的结果,这里 url 就是请求结果
  8. resolve(url)
  9. }, 2000)
  10. })
  11. }
  12. // 需求:
  13. // 1> url: why -> res: why
  14. // 2> url: res + "aaa" -> res: whyaaa
  15. // 3> url: res + "bbb" => res: whyaaabbb
  16. // 1.第一种方案: 多次回调
  17. // 回调地狱
  18. requestData('why').then(res => {
  19. requestData(res + 'aaa').then(res => {
  20. requestData(res + 'bbb').then(res => {
  21. console.log(res)
  22. })
  23. })
  24. })

第二种方案:利用 Promise 中 then 的返回值来解决。
之前第一次请求成功后,在 then 中再次调用 requestData 发送请求,然后再直接 .then。这样就造成回调嵌套。

  1. requestData('why').then(res => {
  2. requestData(res + 'aaa').then() // 在嵌套中调用 then
  3. }

现在我们不直接调用 then 方法了,而是将 requestData 方法调用后 return 出去。requestData 函数返回一个 promise 对象,所以可以将下一个 then 方法,进行链式调用到当前 then 方法的后面。因为这样刚好能取到返回的 promise 对象的请求结果。

  1. // 2.第二种方案: Promise中then的返回值来解决
  2. requestData("why").then(res => {
  3. return requestData(res + "aaa") // 这次请求的结果会作为 then 返回的 promise 的结果
  4. }).then(res => { // 这个 then 方法可以获取到回调中返回的 promise 的请求结果
  5. return requestData(res + "bbb")
  6. }).then(res => {
  7. console.log(res)
  8. })

但是这样阅读性还是不够清晰。
所以推出了第三种方案:promise + generator

  1. // 先封装一个生成器函数
  2. function* getData() {
  3. yield requestData('why') // yield 会返回 value 为 promise 的对象
  4. }
  5. // 实现生成器函数
  6. const generator = getData()
  7. generator
  8. .next() // 拿到两个属性的对象
  9. .value // 从对象中取出了 promise 对象
  10. .then(res => {
  11. // 这里就拿到了当然 requestData 方法中 promise 的请求结果
  12. // 但是我们这里拿到了没有用啊,我们想要拿到结果能给下一次的 promise 请求
  13. })

这个时候我们就要想到 next 函数传递参数了,next 函数的参数会作为上一个 yield 表达式的返回值。

  1. // 先封装一个生成器函数
  2. function* getData() {
  3. // res1 接收到了 next 的传参,也就是 res1 拿到了 requestData('why') 的请求结果
  4. const res1 = yield requestData('why')
  5. yield requestData(res1 + 'bbb') // 第二次的 requestData 就接收到了上一次请求的结果
  6. }
  7. // 执行生成器函数
  8. const generator = getData()
  9. generator
  10. .next()
  11. .value
  12. .then(res => {
  13. // 这里就拿到了当前 requestData 方法中 promise 的请求结果 res
  14. // 但是我们这里拿到了没有用啊,我们想要拿到结果能给下一次的 promise 请求
  15. generator.next(res) // 这样一传参,就把 res 传给了上一个 yield 的返回值
  16. })
  1. function* getData() {
  2. const res1 = yield requestData('why') // 多个请求就这样分块执行下去
  3. const res2 = yield requestData(res1 + 'bbb')
  4. yield requestData(res2 + 'ccc')
  5. }
  6. // 执行生成器函数
  7. const generator = getData()
  8. generator.next() .value .then(res => {
  9. generator.next(res).value.then(res => {
  10. generator.next(res).value.then(res => {
  11. console.log(res) // whybbbccc
  12. })
  13. })
  14. })

现在发送请求部分是分块了,很清晰,但是执行生成器函数的时候,又回调地狱了。但是这个回调是很有规律的,可以利用递归解决。

  1. // 封装一个工具函数execGenerator自动执行生成器函数
  2. function execGenerator(genFn) {
  3. // 获取生成器
  4. const generator = genFn()
  5. // 递归函数
  6. function exec(res) {
  7. // 手动执行第一个步
  8. const result = generator.next(res)
  9. // 停止条件
  10. if (result.done) return result.value
  11. // 停止条件不成立,开始递归执行
  12. result.value.then(res => {
  13. exec(res)
  14. })
  15. }
  16. exec()
  17. }

除了手动封装生成器函数执行函数,还可以使用第三方库:co

  1. // npm i co
  2. const co = require('co')
  3. co(getData) // 将生产器函数传进去就可以了

终极方案:async/await