• 忘掉天地
  • 仿佛也想不起自己
bingliaolongBingliaolong  2025-04-20 14:21 Aet 隐藏边栏 |   抢沙发  11 
文章评分 3 次,平均分 5.0

generator

概述

  1. 常规函数只会返回一个单一值(或者不返回任何值)
  2. generator 可以按需一个接一个地返回(“yield”)多个值
    1. 它们可与 iterable 完美配合使用,从而可以轻松地创建数据流

generator 函数

  1. 要创建一个 generator,我们需要一个特殊的语法结构:function*,即所谓的 “generator function

  1. generator 函数与常规函数的行为不同
    1. 在此类函数被调用时,它不会运行其代码
    2. 而是返回一个被称为 “generator object” 的特殊对象,来管理执行流程

  1. 到目前为止,上面这段代码中的 函数体 代码还没有开始执行:

    1. 一个 generator 的主要方法就是 next()
    2. 当被调用时,它会恢复运行,执行直到最近的 yield <value> 语句(value 可以被省略,默认为 undefined
    3. 然后函数执行暂停,并将产出的(yielded)值返回到外部代码
  2. next() 的结果始终是一个具有两个属性的对象:

    1. value: 产出的(yielded)的值
    2. done: 如果 generator 函数已执行完成则为 true,否则为 false
  3. 例如,我们可以创建一个 generator 并获取其第一个产出的(yielded)值:

    1. 截至目前,我们只获得了第一个值,现在函数执行处在第二行:

  1. 再次调用 generator.next()。代码恢复执行并返回下一个 yield 的值:
    1. 如果我们第三次调用 generator.next(),代码将会执行到 return 语句,此时就完成这个函数的执行:

  1. 现在 generator 执行完成。我们通过 done:true 可以看出来这一点,并且将 value:3 处理为最终结果
    1. 再对 generator.next() 进行新的调用不再有任何意义。如果我们这样做,它将返回相同的对象:{done: true}

generator 是可迭代的

  1. 可以使用 for..of 循环遍历它所有的值:
    1. 这个例子会先显示 1,然后是 2,然后就没了。它不会显示 3

  1. 这是因为当 done: true 时,for..of 循环会忽略最后一个 value
    1. 因此,如果我们想要通过 for..of 循环显示所有的结果,我们必须使用 yield 返回它们:

  1. 因为 generator 是可迭代的,我们可以使用 iterator 的所有相关功能
    1. 例如:spread 语法 ...
    2. 这段代码中,...generateSequence() 将可迭代的 generator 对象转换为了一个数组

使用 generator 进行迭代

  1. 下面代码创建了一个可迭代的 range 对象,它返回 from..to 的值

  1. 我们可以通过提供一个 generator 函数作为 Symbol.iterator,来使用 generator 进行迭代:
    1. 之所以代码正常工作,是因为 range[Symbol.iterator]() 现在返回一个 generator,而 generator 方法正是 for..of 所期望的:
    2. 它具有 .next() 方法
    3. 它以 {value: ..., done: true/false} 的形式返回值
    4. 这不是巧合。generator 被添加到 JavaScript 语言中是有对 iterator 的考量的,以便更容易地实现 iterator
    5. 带有 generator 的变体比原来的 range 迭代代码简洁得多,并且保持了相同的功能

注意

  1. generator 可以永远产出(yield)值
    1. 上面的示例中,我们生成了有限序列,但是我们也可以创建一个生成无限序列的 generator,它可以一直产出(yield)值
    2. 例如,无序的伪随机数序列
    3. 这种情况下肯定需要在 generatorfor..of 循环中添加一个 break(或者 return)。否则循环将永远重复下去并挂起

generator 组合

  1. generator 组合(composition)是 generator 的一个特殊功能,它允许透明地(transparently)将 generator 彼此“嵌入(embed)”到一起
  2. 例如,我们有一个生成数字序列的函数:

  1. 现在,我们想重用它来生成一个更复杂的序列:
    1. 对于 generator 而言,我们可以使用 yield* 这个特殊的语法来将一个 generator “嵌入”(组合)到另一个 generator 中:
    2. yield* 指令将执行 委托 给另一个 generator
    3. 这个术语意味着 yield* gengenerator gen 上进行迭代,并将其产出(yield)的值透明地(transparently)转发到外部。就好像这些值就是由外部的 generator yield 的一样

yield” 是一条双向路

  1. 目前看来,generator 和可迭代对象类似,都具有用来生成值的特殊语法
    1. 但实际上,generator 更加强大且灵活
  2. 这是因为 yield 是一条双向路(two-way street):它不仅可以向外返回结果,而且还可以将外部的值传递到 generator
    1. 调用 generator.next(arg),我们就能将参数 arg 传递到 generator 内部
    2. 这个 arg 参数会变成 yield 的结果
    3. 第一次调用 generator.next() 应该是不带参数的(如果带参数,那么该参数会被忽略)
    4. 它开始执行并返回第一个 yield "2 + 2 = ?" 的结果。此时,generator 执行暂停,而停留在 (*) 行上
    5. generator.next(4)generator 恢复执行,并获得了 4 作为结果:let result = 4
    6. 请注意,外部代码不必立即调用 next(4)。外部代码可能需要一些时间。这没问题:generator 将等待它

  1. 可以看到,与常规函数不同,generator 和调用 generator 的代码可以通过在 next/yield 中传递值来交换结果
    1. 第一个 .next() 启动了 generator 的执行……执行到达第一个 yield
    2. 结果被返回到外部代码中
    3. 第二个 .next(4)4 作为第一个 yield 的结果传递回 generator 并恢复 generator 的执行
    4. 执行到达第二个 yield,它变成了 generator 调用的结果
    5. 第三个 next(9)9 作为第二个 yield 的结果传入 generator 并恢复 generator 的执行,执行现在到达了函数的最底部,所以返回 done: true

generator.throw

  1. 正如我们在上面的例子中观察到的那样,外部代码可能会将一个值传递到 generator,作为 yield 的结果
    1. 但是它也可以在那里发起(抛出)一个 error。这很自然,因为 error 本身也是一种结果
    2. 要向 yield 传递一个 error,我们应该调用 generator.throw(err)。在这种情况下,err 将被抛到对应的 yield 所在的那一行
    3. (2) 行引入到 generatorerror 导致了在 (1) 行中的 yield 出现了一个异常

  1. 如果我们没有捕获它,那么就会像其他的异常一样,它将从 generator “掉出”到调用代码中

generator.return

  1. generator.return(value) 完成 generator 的执行并返回给定的 value
    1. 如果我们在已完成的 generator 上再次使用 generator.return(),它将再次返回该值
    2. 通常我们不使用它,因为大多数时候我们想要获取所有的返回值,但是当我们想要在特定条件下停止 generator 时它会很有用

异步迭代和 generator

概述

  1. 异步迭代允许我们对按需通过异步请求而得到的数据进行迭代
  2. 例如,我们通过网络分段(chunk-by-chunk)下载数据时。异步生成器(generator)使这一步骤更加方便

示例

  1. 假设我们有一个对象,例如下面的 range
    1. 我们想对它使用 for..of 循环,例如 for(value of range),来获取从 15 的值
    2. 换句话说,我们想向对象 range 添加 迭代能力
    3. 这可以通过使用一个名为 Symbol.iterator 的特殊方法来实现:
    4. 当循环开始时,该方法被 for..of 结构调用,并且它应该返回一个带有 next 方法的对象
    5. 对于每次迭代,都会为下一个值调用 next() 方法
    6. next() 方法应该以 {done: true/false, value:<loop value>} 的格式返回一个值,其中 done:true 表示循环结束

异步可迭代对象

  1. 当值是以异步的形式出现时,例如在 setTimeout 或者另一种延迟之后,就需要异步迭代
    1. 最常见的场景是,对象需要发送一个网络请求以传递下一个值
  2. 要使对象异步迭代:
    1. 使用 Symbol.asyncIterator 取代 Symbol.iterator
    2. next() 方法应该返回一个 promise(带有下一个值,并且状态为 fulfilled
      关键字 async 可以实现这一点,我们可以简单地使用 async next()
    3. 我们应该使用 for await (let item of iterable) 循环来迭代这样的对象
  3. 创建一个可迭代的 range 对象,与前面的那个类似,不过现在它将异步地每秒返回一个值
    1. 为了使一个对象可以异步迭代,它必须具有方法 Symbol.asyncIterator (1)
    2. 这个方法必须返回一个带有 next() 方法的对象,next() 方法会返回一个 promise (2)
    3. 这个 next() 方法可以不是 async 的,它可以是一个返回值是一个 promise 的常规的方法,但是使用 async 关键字可以允许我们在方法内部使用 await,所以会更加方便
    4. 我们使用 for await(let value of range) (4) 来进行迭代,也就是在 for 后面添加 await
      它会调用一次 range[Symbol.asyncIterator]() 方法一次,然后调用它的 next() 方法获取值

注意

  1. 需要常规的同步 iterator 的功能,无法与异步 iterator 一起使用
    1. 例如,spread 语法无法工作:
    2. 这很正常,因为它期望找到 Symbol.iterator,而不是 Symbol.asyncIterator
    3. for..of 的情况和这个一样:没有 await 关键字时,则期望找到的是 Symbol.iterator

回顾 generator

  1. Generator 是标有 function*(注意星号)的函数,它使用 yield 来生成值,并且我们可以使用 for..of 循环来遍历它们

  1. 正如我们所知道的,要使一个对象可迭代,我们需要给它添加 Symbol.iterator

  1. 对于 Symbol.iterator 来说,一个通常的做法是返回一个 generator,这样可以使代码更短,如下所示:

异步 generator (finally)

  1. 对于大多数的实际应用程序,当我们想创建一个异步生成一系列值的对象时,我们都可以使用异步 generator
  2. 语法很简单:在 function* 前面加上 async。这即可使 generator 变为异步的
  3. 然后使用 for await (...) 来遍历它,像这样:
  4. 因为此 generator 是异步的,所以我们可以在其内部使用 await,依赖于 promise,执行网络请求等任务

异步的可迭代对象 range

  1. 常规的 generator 可用作 Symbol.iterator 以使迭代代码更短
  2. 与之类似,异步 generator 可用作 Symbol.asyncIterator 来实现异步迭代
    1. 例如,我们可以通过将同步的 Symbol.iterator 替换为异步的 Symbol.asyncIterator,来使对象 range 异步地生成值,每秒生成一个:

实际的例子:分页的数据

  1. 目前,有很多在线服务都是发送的分页的数据(paginated data
    1. 例如,当我们需要一个用户列表时,一个请求只返回一个预设数量的用户(例如 100 个用户)—— “一页”,并提供了指向下一页的 URL
  2. 这种模式非常常见。不仅可用于获取用户列表,这种模式还可以用于任意东西
    1. 例如,GitHub 允许使用相同的分页提交(paginated fashion)的方式找回 commit
    2. 我们应该以 https://api.github.com/repos/<repo>/commits 格式创建进行 fetch 的网络请求
    3. 它返回一个包含 30commitJSON,并在返回的 Link header 中提供了指向下一页的链接
    4. 然后我们可以将该链接用于下一个请求,以获取更多 commit,以此类推
  3. 对于我们的代码,我们希望有一种更简单的获取 commit 的方式
    1. 让我们创建一个函数 fetchCommits(repo),用来在任何我们有需要的时候发出请求,来为我们获取 commit
    2. 并且,该函数能够关注到所有分页内容
    3. 对于我们来说,它将是一个简单的 for await..of 异步迭代

  1. 通过异步 generator,我们可以轻松实现上面所描述的函数,如下所示:

本文为原创文章,版权归所有,欢迎分享本文,转载请保留出处!

bingliaolong
Bingliaolong 关注:0    粉丝:0
Everything will be better.

发表评论

表情 格式 链接 私密 签到
扫一扫二维码分享