zoukankan      html  css  js  c++  java
  • 深入理解 ES6 Generator

    一、什么是 Generator

    Generator 用来控制循环流程,主要解决异步编程嵌套层级较深的问题。

    二、ES6 如何让遍历“停”下来

    ES5 中循环一旦执行则无法停下

    function loop() {
      for (let i = 0; i < 5; i++) {
        console.log(i)
      }
    }
    
    loop()
    // 0
    // 1
    // 2
    // 3
    // 4
    

    而使用 ES6 Generator 可以将执行的循环停下,步骤如下:
    1、在 loop 前面加一个星号
    2、在输出前面加 yield
    3、定义一个变量将 loop 赋值给 l

    function* loop() {
      for (let i = 0; i < 5; i++) {
        yield console.log(i)
      }
    }
    const l = loop()
    
    // 这个时候并没有输出,若要输出调用 next 方法
    l.next() // 0
    l.next() // 1
    l.next() // 2
    l.next() // 3
    l.next() // 4
    l.next() // 之后不会输出任何东西
    

    可用于年会抽奖、自定义遍历器等场景

    三、Basic Syntax —— 基础语法

    1、遍历器就是一个函数,但与普通的函数不同,形式上多了一个 *

    2、函数内部使用 yield 停下来

    3、调用时不会立即执行而是返回一个生成器对象

    4、返回的生成器对象调用 next 来控制循环

    5、Generator 函数的定义不能使用箭头函数,否则会触发报错 SyntaxError

    function* gen() {
      let val
      val = yield 1
      console.log(val)
    }
    
    const l = gen()
    console.log(l)  // gen {<suspended>}
    
    l.next() // 没有任何输出
    l.next() // undefined  yield 表达式没有返回值,所以返回 undefined
    

    6、next() 的返回值

    ① 第一个参数:返回的值
    ② 第二个参数:done 属性,表示是否遍历完成,false 是没有遍历完,true 是遍历完成

    7、再执行一次 next() 方法会继续执行

    function* gen() {
      let val
      val = yield [1, 2, 3]
      console.log(val) // undefined
    }
    
    const l = gen()
    
    console.log(l.next()) // {value: Array(3), done: false}
    console.log(l.next()) // {value: undefined, done: true}
    
    function* gen() {
      let val
      // yield 后面加了一个星号,后面是一个遍历的对象,所以可以嵌套一个 Generator对象
      val = yield* [1, 2, 3]
      console.log(val) // undefined
    }
    
    const l = gen()
    
    console.log(l.next()) // {value: 1, done: false}
    console.log(l.next()) // {value: 2, done: false}
    

    扩展

    1、yield 有没有返回值?

    没有,但是遍历器对象的 next 方法可以修改这个默认值

    2、和 ES5 相比,是如何控制程序的停止和启动的?

    使用 yield 去控制停止,使用 next 去控制启动

    四、Senior Syntax —— 高级语法

    1、next 添加参数

    next 函数写参数,作为 yield 的返回值

    function* gen() {
      let val
      val = yield [1, 2, 3]
      console.log(val) // 20
    }
    
    const l = gen()
    
    console.log(l.next(10))// {value: Array(3), done: false}
    // 此时 yield 没有赋值,所以 10 并没有用
    console.log(l.next(20))// {value: undefined, done: true}
    // 此时 yield 对 val 进行赋值操作,yield 表达式的值是 20
    

    再举个例子:

    function* gen() {
      var val = 100
      while (true) {
        console.log(`before${val}`)
        val = yield val
        console.log(`return ${val}`)
      }
    }
    
    let g = gen()
    console.log(g.next(20).value)
    // before 100
    // 100
    console.log(g.next(30).value)
    // return 30
    // before 30
    // 30
    console.log(g.next(40).value)
    // return 40
    // before 40
    // 40
    

    g.next(20) 这句代码会执行 gen 内部的代码,遇到第一个 yield 暂停,所以 console.log("before "+val) 执行输出了 before 100,此时的 val100,所以执行到 yield val 返回了 100,注意 yield val 并没有赋值给 val

    g.next(30) 这句代码会继续执行 gen 内部的代码,也就是 val = yield val 这句,因为 next 传入了 30,所以 yield val 这个返回值就是 30,因此 val 被赋值 30,执行到 console.log("return "+val) 输出了 30,此时没有遇到 yield 代码继续执行,也就是 while 的判断,继续执行 console.log("before "+val) 输出了 before 30,再执行遇到了 yield val 程序暂停。

    g.next(40) 重复步骤 2

    2、return 控制结束

    function* gen() {
      let val
      val = yield [1, 2, 3]
      console.log(val) // 没有执行
    }
    
    const l = gen()
    
    console.log(l.next(10))// {value: Array(3), done: false}
    console.log(l.return())// {value: undefined, done: true}
    // 返回操作,函数终止
    console.log(l.next(20))// {value: undefined, done: true}
    

    添加返回值的参数

    function* gen() {
      let val
      val = yield [1, 2, 3]
      console.log(val) // 没有执行
    }
    
    const l = gen()
    
    console.log(l.next(10))// {value: Array(3), done: false}
    console.log(l.return(100))// {value: 100, done: true}
    // 返回操作,函数终止
    console.log(l.next(20))// {value: undefined, done: true}
    

    3、throw 抛出异常控制

    function* gen() {
      while (true) {
        try {
          yield 1
        } catch (e) {
          console.log(e.message) // ss
        }
      }
    }
    
    const l = gen()
    
    console.log(l.next())//{value: 1, done: false}
    console.log(l.next())//{value: 1, done: false}
    console.log(l.next())//{value: 1, done: false}
    
    l.throw(new Error('ss'))
    // 抛出错误,执行 catch
    console.log(l.next()) //{value: 1, done: false}
    

    四、Generator 异步方案

    Generator 可用于解决异步编程嵌套层级较深的问题。
    虽然使用 Promise 没有了大量的嵌套代码,但是依然有大量的回调函数,可读性依然不好。

    // Promise chain
    ajax('/api/url1')
      .then(value => {
        return ajax('ajax/url2')
      })
      .then(value => {
        return ajax('ajax/url3')
      })
      .then(value => {
        return ajax('ajax/url4')
      })
      .catch(error => {
        console.error(error)
      })
    

    要解决上面的问题,就要用到 Generator 生成器函数:

    // 定义一个 generator 函数,ajax 返回一个 promise 对象
    function* main() {
      try {
        const users = yield ajax('/api/users.json')
        console.log(users)
    
        const posts = yield ajax('/api/posts.json')
        console.log(posts)
    
        const urls = yield ajax('/api/urls.json')
        console.log(urls)
      } catch (e) {
        // 捕获异常
        console.log(e)
      }
    }
    
    const g = main()
    
    // 定义一个递归函数
    function handlerResult(result) {
      if (result.done) return // 如果为 true,退出递归调用
      // result.value 返回是一个 promise 对象,使用 then 可以执行其结果
      result.value.then(data => {
        // g.next(data) 可以作为 yield 返回值,再进入下一次递归
        handlerResult(g.next(data))
        // 异常逻辑
      }, error => {
        g.throw(error)
      })
    }
    
    handleResult(g.next())
    

    五、Generator 应用实例

    1、实现抽奖

    ① ES5 做法

    function draw(first = 1, second = 3, third = 5) {
      // 三个奖的候选人,一个结果,一个随机数
      let firstPrize = ['1A', '1B', '1C', '1D', '1E']
      let secondPrize = ['2A', '2B', '2C', '2D', '2E', '2F', '2G', '2H', '2I', '2J', '2K', '2L']
      let thirdPrize = ['3A', '3B', '3C', '3D', '3E', '3F', '3G', '3H', '3I', '3J', '3K', '3L', '3M', '3N', '3O', '3P', '3Q', '3R', '3S', '3T', '3U', '3V', '3W', '3X', '3Y', '3Z']
      let result = []
      let random
      // 抽一等奖
      for (let i = 0; i < first; i++) {
        random = Math.floor(Math.random() * firstPrize.length)
        result = result.concat(firstPrize.splice(random, 1))
      }
      // 抽二等奖
      for (let i = 0; i < second; i++) {
        random = Math.floor(Math.random() * secondPrize.length)
        result = result.concat(secondPrize.splice(random, 1))
      }
      // 抽三等奖
      for (let i = 0; i < third; i++) {
        random = Math.floor(Math.random() * thirdPrize.length)
        result = result.concat(thirdPrize.splice(random, 1))
      }
      return result
    }
    
    console.log(draw())
    // ["1A", "2D", "2K", "2A", "3A", "3G", "3Y", "3W", "3P"]
    

    ② ES6 做法

    function* draw(first = 1, second = 3, third = 5) {
      // 三个奖的候选人,一个结果,一个随机数
      let firstPrize = ['1A', '1B', '1C', '1D', '1E']
      let secondPrize = ['2A', '2B', '2C', '2D', '2E', '2F', '2G', '2H', '2I', '2J', '2K', '2L']
      let thirdPrize = ['3A', '3B', '3C', '3D', '3E', '3F', '3G', '3H', '3I', '3J', '3K', '3L', '3M', '3N', '3O', '3P', '3Q', '3R', '3S', '3T', '3U', '3V', '3W', '3X', '3Y', '3Z']
      let count = 0
      let random
      while (1) {
        if (count < first) {
          random = Math.floor(Math.random() * firstPrize.length)
          yield firstPrize[random]
          count++
          firstPrize.splice(random, 1)
        } else if (count < first + second) {
          random = Math.floor(Math.random() * secondPrize.length)
          yield secondPrize[random]
          count++
          secondPrize.splice(random, 1)
        } else if (count < first + second + third) {
          random = Math.floor(Math.random() * thirdPrize.length)
          yield thirdPrize[random]
          count++
          thirdPrize.splice(random, 1)
        } else {
          return false
        }
      }
    
    }
    
    let d = draw()
    console.log(d.next().value) // 1C
    console.log(d.next().value) // 2E
    console.log(d.next().value) // 2H
    console.log(d.next().value) // 2C
    console.log(d.next().value) // 3H
    console.log(d.next().value) // 3V
    console.log(d.next().value) // 3A
    console.log(d.next().value) // 3J
    console.log(d.next().value) // 3N
    console.log(d.next().value) // false
    console.log(d.next().value) // undefined
    console.log(d.next().value) // undefined
    

    2、实现数 3 的倍数小游戏

    如果是 ES5,会无限死循环,导致程序崩溃

    ES6 做法:

    function* count(x = 1) {
      while (1) {
        if (x % 3 === 0) {
          yield x
        }
        x++
      }
    }
    
    let num = count()
    console.log(num.next().value) // 3
    console.log(num.next().value) // 6
    console.log(num.next().value) // 9
    console.log(num.next().value) // 12
    console.log(num.next().value) // 15
    console.log(num.next().value) // 18
    ...
    

    3、实现 Iterator 方法

    const todos = {
      life: ['吃饭', '睡觉', '打豆豆'],
      learn: ['语文', '数学', '外语'],
      work: ['喝茶'],
    
      each: function (callback) {
        const all = [].concat(this.life, this.learn, this.work)
        for (const item of all) {
          callback(item)
        }
      },
      [Symbol.iterator]: function* () {
        const all = [...this.life, ...this.learn, ...this.work]
        for (const item of all) {
          yield item
        }
      }
    }
    
    for (const item of todos) {
      console.log(item)
    }
    // 吃饭
    // 睡觉
    // 打豆豆
    // 语文
    // 数学
    // 外语
    // 喝茶
    
  • 相关阅读:
    CSAPP阅读笔记-栈帧-来自第三章3.7的笔记-P164-P176
    CSAPP阅读笔记-汇编语言初探(控制类指令)-来自第三章3.6的笔记-P135-P163
    CSAPP阅读笔记-汇编语言初探(算术和逻辑操作类指令)-来自第三章3.5的笔记-P128-P135
    CSAPP阅读笔记-汇编语言初探(数据传送类指令)-来自第三章3.2-3.3的笔记-P115-P128
    CSAPP阅读笔记-gcc常用参数初探-来自第三章3.2的笔记-P113
    CSAPP阅读笔记-32位64位的区别--来自第三章引言的笔记--P110
    写在前面
    VS2010禁用IntelliSense提示
    VS2012编译64位程序
    VS2012错误之:warning LNK4075: 忽略“/EDITANDCONTINUE”(由于“/SAFESEH”规范)
  • 原文地址:https://www.cnblogs.com/Leophen/p/14900172.html
Copyright © 2011-2022 走看看