Skip to content

一. 迭代器和可迭代对象


1. 什么是迭代器?

  • 迭代器(iterator,使用户在容器对象(container,例如链表或数组)上遍访的对象,使用该接口无需关心对象的内部实现细节

    • 其行为像数据库中的光标,迭代器最早出现在1974年设计的 CLU 编程语言中
    • 在各种编程语言的实现中,迭代器的实现方式各不相同,但是基本都有迭代器,比如 JavaPython
  • 从迭代器的定义我们可以看出来,迭代器是 用来对某个数据结构进行遍历的对象

  • js 中,迭代器也是一个具体的对象,这个对象需要符合迭代器协议iterator protocol):

  • 迭代器协议定义了产生一系列值(无论是有限还是无限个)的标准方式

  • js 中这个标准就是一个特定的 next 方法

  • next 方法有如下的要求:

    • 一个无参数或一个参数的函数,返回一个应当拥有以下两个属性的对象

    • done(boolean)

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

      • 迭代器返回的任何 js 值。donetrue 时可省略,省略等价于 undefined
      js
      const names = ["abc", "cba", "nba"]
      // 给数组 names 创建一个迭代器(迭代器: names的迭代器)
      let index = 0
      const namesIterator = {
        next: function() {
          // done: Boolean
          // value: 具体值/undefined
          if (index < names.length) {
            return { done: false, value: names[index++] }
          } else {
            return { done: true } // 等价于 { done: true, value: undefined },所以可省略value
          }
        }
      }
      
      namesIterator.next() // {done: false, value: 'abc'}
      namesIterator.next() // {done: false, value: 'cba'}
      namesIterator.next() // {done: false, value: 'nba'}
      namesIterator.next() // {done: true}
    • 封装一个迭代器函数

      js
      const names = ["abc", "cba", "nba"]
      const nums = [100, 24, 55, 66, 86]
      
      // 封装一个函数
      function createArrayIterator(arr) {
        let index = 0
        return {
          next: function() {
            if (index < arr.length) {
              return { done: false, value: arr[index++] }
            } else {
              return { done: true }
            }
          }
        }
      }
      
      const namesIterator = createArrayIterator(names)
      namesIterator.next() // {done: false, value: 'abc'}
      namesIterator.next() // {done: false, value: 'cba'}
      namesIterator.next() // {done: false, value: 'nba'}
      namesIterator.next() // {done: true}
      const numsIterator = createArrayIterator(nums)
      numsIterator.next() // {done: false, value: 100}
      numsIterator.next() // {done: false, value: 24}
      numsIterator.next() // {done: false, value: 55}
      numsIterator.next() // {done: false, value: 66}
      numsIterator.next() // {done: false, value: 86}
      numsIterator.next() // {done: true }

2. 什么是可迭代对象?

  • 但是上面的代码整体来说看起来是有点奇怪的:

    • 我们获取一个数组的时候,需要自己创建一个 index 变量,再创建一个所谓的迭代器对象
    • 事实上我们可以对上面的代码进行进一步的封装,让其变成一个可迭代对象
  • 什么又是可迭代对象呢?

    • 它和迭代器是不同的概念
    • 当一个对象实现了 iterable protocol 协议时它就是一个可迭代对象
    • 这个对象的要求是必须实现 @@iterator 方法在代码中我们使用 Symbol.iterator 访问该属性
  • 当然我们要问一个问题,我们转成这样的一个东西有什么好处呢?

    • 当一个对象变成一个可迭代对象的时候,就可以进行某些迭代操作
    • 比如 for...of 操作时,其实就会调用它的 @@iterator 方法
    js
    // 将 infos 变成一个可迭代对象
    /*
      1. 必须实现一个特定的函数: [Symbol.iterator]
      2. 这个函数需要返回一个迭代器(这个迭代器用于迭代当前的对象)
    */
    // 1.迭代infos中friends
    const infos = {
      friends: ["kobe", "james", "curry"],
      [Symbol.iterator]: function() {
        let index = 0
        const infosIterator = {
          next: () => {
            if (index < this.friends.length) {
              return { done: false, value: this.friends[index++] }
            } else {
              return { done: true }
            }
          }
        }
        return infosIterator
      }
    }
    
    // 可迭代对象必然具备下面的特点
    // 可迭对象可以进行 for...of 操作
    for (const item of infos) {
      console.log(item) // kobe james curry
    }
    
    // 可迭代对象必然有一个 [Symbol.iterator] 函数
    // 数组是一个可迭代对象
    const students = ["张三", "李四", "王五"]
    students[Symbol.iterator] // ƒ values() { [native code] }
    const studentIterator = students[Symbol.iterator]()
    studentIterator.next() // {value: '张三', done: false}
    studentIterator.next() // {value: '李四', done: false}
    studentIterator.next() // {value: '王五', done: false}
    studentIterator.next() // {value: undefined, done: true}
    
    // 2.迭代infos2中的key-value
    const infos2 = {
      name: "later",
      age: 18,
      height: 1.88,
      [Symbol.iterator]: function() {
        const keys = Object.keys(this) // 获取对象自身中所有可枚举属性的key(symbol除外)
        const values = Object.values(this) // 获取对象自身所有可枚举属性的value(symbol除外)
        const entries = Object.entries(this) // 获取对象自身所有可枚举属性的key-value
        let index = 0
        const iterator = {
          next: function() {
            if (index < entries.length) {
              return { done: false, value: entries[index++] }
            } else {
              return { done: true }
            }
          }
        }
        return iterator
      }
    }
    
    // 可迭对象可以进行for...of操作
    for (const item of infos2) {
      const [key, value] = item
      console.log(key, value) 
    }
    // name later
    // age 18
    // height 1.88

二. 原生的迭代器对象


1. 原生迭代器对象

  • 事实上我们平时创建的很多原生对象已经实现了可迭代协议,会生成一个迭代器对象的:

    • StringArrayMapSetarguments 对象、NodeList 集合
    js
    // 1.数组
    const names = ["abc", "cba", "nba"]
    for (const name of names) {
      console.log(name)
    }
    names[Symbol.iterator]() // Array Iterator {}
    names[Symbol.iterator] == Array.prototype[Symbol.iterator] // true
    
    // 2.Set
    const set = new Set(["abc", "cba", "nba"])
    for (const item of set) {
      console.log(item)
    }
    const SetIterator = set[Symbol.iterator]()
    SetIterator.next() // {value: 'abc', done: false}
    SetIterator.next() // {value: 'cba', done: false}
    SetIterator.next() // {value: 'nba', done: false}
    SetIterator.next() // {value: undefined, done: true}
    set[Symbol.iterator]() // SetIterator {'abc', 'cba', 'nba'}
    set[Symbol.iterator] == Set.prototype[Symbol.iterator] // true
    
    // 3.arguments
    function foo() {
      for (const arg of arguments) {
        console.log(arg)
      }
      console.log(arguments[Symbol.iterator]()) // Array Iterator {}
      console.log(arguments[Symbol.iterator] == Array.prototype[Symbol.iterator]) // true
    }
    
    foo(123, 321, 111, 222)

2. 可迭代对象的应用

  • 那么这些东西可以被用在哪里呢?

    • js 中语法:

      js
      for...of
      
      展开语法spread syntax
      
      yield*后面章节讲解
      
      解构赋值Destructuring_assignment
    • 创建一些对象时:

      js
      new Map([Iterable])
      new WeakMap([iterable])
      
      new Set([iterable])
      new WeakSet([iterable])
    • 一些方法的调用:

      js
      Promise.all(iterable)
      
      Promise.race(iterable)
      
      Array.from(iterable)
    js
    const arr = [1, 2, 3]
    const info = {
      name: "later",
      age: 18,
      height: 1.88,
      [Symbol.iterator]: function() {
        const values = Object.values(this)
        let index = 0
        const iterator = {
          next: function() {
            if (index < values.length) {
              return { done: false, value: values[index++] }
            } else {
              return { done: true }
            }
          }
        }
        return iterator
      }
    }
    function foo(arg1, arg2, arg3) {
      console.log(arg1, arg2, arg3) 
    }
    // 1.用在特定的语法上:展开语法
    foo(...arr) // 1 2 3
    foo(...info) // later 18 1.88
    
    // 2.一些类的构造方法中, 也是传入的可迭代对象:new Set(iterable)
    const set1 = new Set(["aaa", "bbb", "ccc"])
    const set2 = new Set("abc")
    console.log(set2) // Set(3) {'a', 'b', 'c'}
    const set3 = new Set(info)
    console.log(set3) // Set(3) {'later', 18, 1.88}
    
    // 3.一些常用的方法,如:Promise.all(iterable)
    const p1 = Promise.resolve("aaaa")
    const p2 = Promise.resolve("aaaa")
    const p3 = Promise.resolve("aaaa")
    const pSet = new Set()
    pSet.add(p1)
    pSet.add(p2)
    pSet.add(p3)
    Promise.all(pSet).then(res => {
      console.log("res:", res) // res: ['aaaa', 'aaaa', 'aaaa']
    })
    
    function bar() {
      // 将arguments转成Array类型
      const arr = Array.from(arguments)
      console.log(arr)
    }
    
    bar(111, 222, 333) // [111, 222, 333]

三. 自定义类的迭代器


1. 自定义类的迭代实现

  • 在前面我们看到 ArraySetStringMap 等类创建出来的对象都是可迭代对象:

    • 在面向对象开发中,我们可以通过 class 定义一个自己的类,这个类可以创建很多的对象
    • 如果我们也希望自己的类创建出来的对象默认是可迭代的,那么在设计类的时候我们就可以添加上 @@iterator 方法
  • 案例:

    • 创建一个 classroom 的类
    • 教室中有自己的位置、名称、当前教室的学生
    • 这个教室可以进来新学生(push)
    • 创建的教室对象是可迭代对象
    js
    class Room {
      constructor(position, name, students) {
        this.position = position
        this.name = name
        this.students = students
      }
    
      pushStudent(student) {
        this.students.push(student)
      }
      // 实例方法定义在class内部中,内聚性好于定义在class的原型上
      [Symbol.iterator]() {
        let index = 0
        return {
          next: () => {
            const values = Object.values(this)
            if (index < values.length) {
              return { value: values[index++], done: false }
            } else {
              return { value: undefined, done: true }
            }
          }
        }
      }
    }
    
    const room1 = new Room({x: 1, y: 2}, '教室1', ['小红', '小明'])
    room1.pushStudent('小李')
    
    for (const item of room1) {
      console.log(item) // {x: 1, y: 2} 、教室1、['小红', '小明', '小李']
    }

2. 迭代器的中断

  • 迭代器在某些情况下会在没有完全迭代的情况下中断:

    • 比如遍历的过程中通过 breakreturnthrow 中断了循环操作
    • 比如在解构的时候,没有解构所有的值
  • 那么这个时候我们想要监听中断的话,可以添加 return 方法

    js
    class Room {
      constructor(position, name, students) {
        this.position = position
        this.name = name
        this.students = students
      }
      // 实例方法定义在class内部中,内聚性好于定义在class的原型上
      [Symbol.iterator]() {
        let index = 0
        return {
          next: () => {
            const values = Object.values(this)
            if (index < values.length) {
              return { value: values[index++], done: false }
            } else {
              return { value: undefined, done: true }
            }
          },
          return: () => {
            console.log(this) // Room {position: {…}, name: '教室1', students: ['小红', '小明']}
            return { done: true, value: undefined } //这里如果不return一个对象的话,会报错: Iterator result undefined is not an object
          }
        }
      }
    }
    
    const room1 = new Room({x: 1, y: 2}, '教室1', ['小红', '小明'])
    for (const item of room1) {
      if (item == '教室1') {
        break
      }
      console.log(item) // {x: 1, y: 2}
    }

四. 生成器的理解和作用


1. 什么是生成器函数,什么是生成器?

  • 生成器是 ES6 中新增的一种函数控制、使用的方案,它可以让我们更加灵活的控制函数什么时候继续执行、暂停执行

    • 平时我们会编写很多的函数,这些函数终止的条件通常是返回值或者发生了异常
  • 生成器函数也是一个函数,但是和普通的函数有一些区别:

    • 首先,生成器函数需要在 function 的后面加一个符号:*

    • function* 这种声明方式**(function 关键字后跟一个星号)会定义一个生成器函数**(generator function),调用该函数会返回一个 Generator 对象

      js
      // 下面几种写法都是ok的
      function* generatorFunc() {}
      function * generatorFunc() {}
      function  *generatorFunc() {}
    • 其次,生成器函数可以通过 yield 关键字来控制函数的执行流程

    • 最后,生成器函数的返回值是一个Generator(生成器)

      • 生成器是一种 特殊类型的迭代器
      js
      function* generatorFunc() {}
      console.log( generatorFunc() ) // generatorFunc {<suspended>}
  • GeneratorFunction 构造器生成新的生成器函数对象

  • js 中,生成器函数实际上都是 GeneratorFunction 的实例对象

    • 注意:GeneratorFunction 并不是一个全局对象。它可以通过下面的代码获取

      js
      console.log( Object.getPrototypeOf(function*(){}) ) 
      // GeneratorFunction {prototype: Generator, Symbol(Symbol.toStringTag): 'GeneratorFunction', constructor: ƒ}

2. 生成器函数的执行

  • 我们发现下面的生成器函数 foo 的执行体压根没有执行,它只是返回了一个生成器对象

    • yield 关键字使生成器函数执行暂停yield 关键字后面的表达式的值返回给生成器的调用者,作为 value 的值

    • 它可以被认为是一个基于生成器的版本的 return 关键字

    • yield 关键字返回一个 IteratorResult 对象,它有两个属性 valuedone

    • value 属性是对 yield 表达式求值的结果,而 donefalse,表示生成器函数尚未完全完成

    • 一旦遇到 yield 表达式,生成器的代码将被暂停运行,直到生成器的 next() 方法被调用

    • 那么我们如何可以让它执行函数中的东西呢?调用 next 即可,但是遇到 yield 会暂停执行,除非继续调用该生成器的 next 方法

      js
      function* foo() {
        console.log('111')
        yield
        console.log('222')
      }
      
      foo() // 不会执行函数体代码,而是返回一个生成器对象
      
      const fooGenerator = foo()
      fooGenerator // foo {<suspended>}
      fooGenerator.__proto__ === foo.prototype // true
      foo.prototype // Generator {}
      foo.__proto__ // GeneratorFunction {prototype: Generator, Symbol(Symbol.toStringTag): 'GeneratorFunction', constructor: ƒ}
      
      fooGenerator.next() // 111
      fooGenerator.next() // 222
    • 我们之前学习迭代器时,知道迭代器的 next 是会有返回值的

    • 但是我们很多时候不希望 next 返回的是一个 undefined,这个时候我们可以通过 yield 来返回结果

      js
      function* foo() {
        console.log('执行函数体代码')
      }
      const fooGenerator = foo()
      console.log(fooGenerator.next()) // {value: undefined, done: true}
      
      
      function* bar() {
        console.log('执行函数体代码')
        yield '222'
      }
      const barGenerator = bar()
      console.log(barGenerator.next()) // {value: '222', done: false}

    注意:

    • 在调用 next 时,yield 上面和后面的代码会执行,而前面的不会

      js
      function* bar() {
        console.log('执行函数体代码')
        yield console.log('first')
      }
        
      const barGenerator = bar()
      barGenerator.next()
      // 执行函数体代码
      // first

3. 生成器传递参数 - next 函数

  • 函数既然可以暂停来分段执行,那么函数应该是可以传递参数的,我们是否可以给每个分段来传递参数呢?

    • 答案是可以的
    • 我们在调用 next 函数的时候,可以给它传递参数,这个参数会作为上一个 yield 语句的返回值
    • 注意:也就是说我们是为本次的函数代码块执行提供了一个值
    js
    function* foo(arg1) {
      console.log(arg1) 
      const arg2 = yield
      console.log(arg2)
      const arg3 = yield
      console.log(arg3)
      }
    
    const fooGenerator = foo('参数1')
    // 第一个next方法只能通过函数传参的方式了
    fooGenerator.next() // 参数1 
    fooGenerator.next('参数2') // 参数2
    fooGenerator.next('参数3') // 参数3
  • 如果生成器函数提前 return 返回,后续再调用 next 方法返回 {value: 'undefined', done: true},因为函数已执行完

    js
    function* foo() {
      yield '111'
      return '222'
      yield '333'
    }
    
    const fooGenerator = foo()
    fooGenerator.next() // {value: '111', done: false}
    fooGenerator.next() // {value: '222', done: true}
    fooGenerator.next() // {value: 'undefined', done: true}

4. 生成器提前结束 - return 函数

  • 还有一个可以给生成器函数传递参数的方法是通过 return 函数:

    • return 传值后这个生成器函数就会结束,之后调用 next 不会继续生成值了

      js
      function* foo() {
        console.log('111')
        yield '111'
        console.log('222')
        yield '222'
        console.log('333')
        yield '333'
      }
      
      const fooGenerator = foo()
      fooGenerator.next()
      fooGenerator.return('000')
      fooGenerator.next()
      // output:
      // 111 
      // {value: '111', done: false}
      // {value: '000', done: true}
      // {value: undefined, done: true}

5. 生成器抛出异常 - throw 函数

  • 除了给生成器函数内部传递参数之外,也可以给生成器函数内部抛出异常:

    • throw 方法用来向生成器抛出异常,并恢复生成器的执行,返回带有 donevalue 两个属性的对象

    • 抛出异常后我们可以在生成器函数中捕获异常

    • 但是在 catch 语句中不能继续 yield 新的值了,但是可以在 catch 语句外使用 yield 继续中断函数的执行

    js
    function* foo() {
      console.log('a----')
      try {
        yield '111'
      } catch(err) {
        console.log('err: ', err)
      }
      console.log('b----')
      yield '222'
      console.log('c----')
      yield '333'
    }
    
    const fooGenerator = foo()
    fooGenerator.next()
    fooGenerator.throw('000')
    fooGenerator.next()
    fooGenerator.next()
    // outout:
    // a---- 
    // {value: '111', done: false}
    // err:  000
    // b----
    // {value: '222', done: false}
    // c----
    // {value: '333', done: false}
    // {value: undefined, done: true}

6. 应用题

js
/**
  console.log(1)
  function fn(time){
    setInterval (() => {
      console.log(2)
    },time)
  }
  fn(4)
  console.log(3)

  实现 输出 1 2 3
  只能修改 setInterval 的前后代码 和 内部箭头函数的代码
*/


console.log(1)
function fn(t) {
  let timer = null

  function* foo() {
    timer = setInterval(yield () => {
      if (timer) return clearInterval(timer)
      console.log(2)
    }, t)
  }
  const generatorFn = foo()
  let {value, done} = generatorFn.next() // {value: [Function (anonymous)], done: false}
  // value 就是 setInterval中 yield后面的表达式的值
  value()
  // next方法 传入的参数会作为上一个 yield语句的返回值
  // 所以这里需要传入 setInterval中yield后面的函数,因为 setInterval第一个参数只能是callback
  generatorFn.next(value) 
}
fn(4)
console.log(3)
// output: 
// 1
// 2
// 3

五. 自定义生成器方案


1. 生成器替代迭代器

  • 我们发现生成器是一种特殊类型的迭代器,那么在某些情况下我们可以使用生成器来替代迭代器:

    js
    // 1.对之前的代码进行重构(用生成器函数)
    const names = ["abc", "cba", "nba"]
    
    function* createArrayIterator(arr) {
      for (let i = 0; i < arr.length; i++) {
        yield arr[i]
      }
      // yield arr[0]
      // yield arr[1]
      // yield arr[2]
      // return undefined
    }
    
    const namesIterator = createArrayIterator(names)
    namesIterator.next() // {value: 'abc', done: false}
    namesIterator.next() // {value: 'cba', done: false}
    namesIterator.next() // {value: 'nba', done: false}
    namesIterator.next() // {value: undefined, done: true}
    
    // 2.生成器函数, 可以生成某个范围的值
    function* createRangeGenerator(start, end) {
      for (let i = start; i < end; i++) {
        yield i
      }
    }
    
    const rangeGen = createRangeGenerator(3, 9)
    rangeGen.next() // {value: 3, done: false}
    rangeGen.next() // {value: 4, done: false}
    rangeGen.next() // {value: 5, done: false}
    rangeGen.next() // {value: 6, done: false}
    rangeGen.next() // {value: 7, done: false}
    rangeGen.next() // {value: 8, done: false}
    rangeGen.next() // {value: undefined, done: true}
  • 事实上我们还可以使用 yield* 来迭代一个可迭代对象:

    • 这个时候相当于是一种 yield 的语法糖,只不过会依次迭代这个可迭代对象每次迭代其中的一个值
    js
    const names = ["abc", "cba", "nba"]
    function* createArrayIterator(arr) {
      yield* arr
    }
    
    const namesIterator = createArrayIterator(names)
    namesIterator.next() // {value: 'abc', done: false}
    namesIterator.next() // {value: 'cba', done: false}
    namesIterator.next() // {value: 'nba', done: false}
    namesIterator.next() // {value: undefined, done: true}

2. 自定义类迭代 - 生成器实现

  • 在之前的自定义类迭代中,我们也可以换成生成器:

    js
    class Room {
      constructor(position, name, students) {
        this.position = position
        this.name = name
        this.students = students
      }
    
      pushStudent(student) {
        this.students.push(student)
      }
      // 实例方法定义在class内部中,内聚性好于定义在class的原型上
      // [Symbol.iterator]() {
      //   let index = 0
      //   return {
      //     next: () => {
      //       const values = Object.values(this)
      //       if (index < values.length) {
      //         return { value: values[index++], done: false }
      //       } else {
      //         return { value: undefined, done: true }
      //       }
      //     }
      //   }
      // }
      *[Symbol.iterator]() {
        yield* Object.values(this)
      }
    }
    
    const room1 = new Room({x: 1, y: 2}, '教室1', ['小红', '小明'])
    room1.pushStudent('小李')
    
    for (const item of room1) {
      console.log(item) // {x: 1, y: 2} 、教室1、['小红', '小明', '小李']
    }

六. 异步处理方案解析


1. 异步处理方案

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

  • 案例需求:

    • 我们需要向服务器发送网络请求获取数据,一共需要发送三次请求
    • 第二次的请求 url 依赖于第一次的结果
    • 第三次的请求 url 依赖于第二次的结果
    js
    function request(url) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(url)
        }, 1000)
      })
    }
    
    // 方式一:层层嵌套(回调地狱)
    function getData() {
      request('aaa').then(res => {
        console.log('第一次res: ', res) // 第一次res:  aaa
    
        request(res + '_bbb').then(res => {
          console.log('第二次res: ', res) // 第二次res:  aaa_bbb
    
          request(res + '_ccc').then(res => {
            console.log('第三次res: ', res) // 第三次res:  aaa_bbb_ccc
          })
        })
      })
    }
    
    getData()
    
    // 方式二:Promise链式调用(解决回调地狱)
    function getData2() {
      // 这种方式还是不行,本质上还是在嵌套回调
      // request('111').then(res => {
      //   console.log('第一次调用: ', res)
      //   return res
      // }).then(res => {
      //   request(res + '_222').then(res => {
      //     console.log('第二次调用: ', res)
      //   })
      // })
      request('111').then(res => {
        console.log('第一次调用:', res) // 第一次调用: 111
        return request(res + '_222')
      }).then(res => {
        console.log('第二次调用:', res) // 第二次调用: 111_222
        return request(res + '_333')
      }).then(res => {
        console.log('第三次调用:', res) // 第三次调用: 111_222_333
      })
    }
    
    getData2()

2. Generator 方案

  • 但是上面的代码其实看起来也是阅读性比较差的,有没有办法可以继续来对上面的代码进行优化呢?

    js
    function request(url) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(url)
        }, 1000)
      })
    }
    // 方式三:生成器函数 + 生成器
    function* getData() {
      const res1 = yield request('111')
      console.log('res1: ', res1)
      const res2 = yield request('222')
      console.log('res2: ', res2)
      const res3 = yield request('333')
      console.log('res3: ', res3)
    }
    
    const generator = getData()
    // console.log(generator.next().value) // Promise {<pending>}
    generator.next().value.then(res1 => {
      generator.next(res1).value.then(res2 => {
        generator.next(res2).value.then(res3 => {
          generator.next(res3)
        })
      })
    })
    
    // 方式四:async + await
    async function getData() {
      const res1 = await request('111')
      const res2 = await request('222')
      const res3 = await request('333')
    }

3. 自动执行 generator 函数

  • 目前我们的写法有两个问题:

    • 第一,我们不能确定到底需要调用几层的 Promise 关系
    • 第二,如果还有其他需要这样执行的函数,我们应该如何操作呢?
    • 难道又往后继续添加代码吗?
  • 所以,我们可以封装一个工具函数 execGenerator 自动执行生成器函数:

    js
    function request(url) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve(url)
        }, 1000)
      })
    }
    
    function* getData() {
      const res1 = yield request('111')
      console.log('res1: ', res1)
      const res2 = yield request('222')
      console.log('res2: ', res2)
      const res3 = yield request('333')
      console.log('res3: ', res3)
    }
    
    function execGenerator(func) {
      const generator = func()
      function exec(res) {
        const result = generator.next(res)
        console.log(result) // {value: Promise, done: false}
        if (result.done) return result.value
        result.value.then(res => {
          exec(res)
        })
      }
      exec()
    }
    
    execGenerator(getData)

4. 总结

  • es6 之前异步代码都是写的嵌套回调函数的方式
  • es6之后,有了 promise,开始使用 promise 的方式写 Promise 链式调用的代码
  • 然后发现可以使用生成器写成方式三中的代码,但是需要写一个自动执行 generator 函数的函数
  • 于是 asyncawait 关键字就出来了,底层原理主要就是 generator + promise 写法的语法糖
  • 也有一点区别,就比如自动执行生成器函数中 yield 需要搭配 promise 使用
  • await 做了一些边界值判断,允许使用普通值

Released under the MIT License.