掌握JavaScript中的迭代器和生成器(下)

简介: 掌握JavaScript中的迭代器和生成器(下)

掌握JavaScript中的迭代器和生成器(上)https://developer.aliyun.com/article/1411466


(3)yield 运算符

生成器为 JavaScript 引入了一个新的关键字:yield**yield**** 可以暂停生成器函数并返回 **yield** 之后的值,从而提供一种轻量级的方法来遍历值。**

在下面的例子中,我们将使用不同的值暂停生成器函数三次,并在最后返回一个值。 然后将生成器对象分配给 generator 变量。

javascript

复制代码

function* generatorFunction() {
  yield 'One'
  yield 'Two'
  yield 'Three'
  return 'Hello, Generator!'
}
const generator = generatorFunction()

现在,当我们在生成器函数上调用 next() 时,它会在每次遇到 yield 时暂停。 done 会在每次 yield 后设置为 false,表示生成器还没有结束。 一旦遇到 return,或者函数中没有更多的 yield 时,done 就会变为 true,生成器函数就结束了。

连续四次调用 next() 方法:

javascript

复制代码

generator.next()
generator.next()
generator.next()
generator.next()

这些将按顺序得到以下结果:

{value: "One", done: false}
{value: "Two", done: false}
{value: "Three", done: false}
{value: "Hello, Generator!", done: true}

next() 非常适合从迭代器对象中提取有限数据。

注意,生成器不需要 return; 如果省略,最后一次迭代将返回 {value: undefined, done: true},生成器完成后对 next() 的任何后续调用也是如此。

(4)遍历生成器

使用 next() 方法可以遍历生成器对象,接收完整对象的所有 valuedone 属性。 不过,就像 Array、Map 和 Set 一样,Generator 遵循迭代协议,并且可以使用 for...of 进行迭代:

function* generatorFunction() {
  yield 'One'
  yield 'Two'
  yield 'Three'
  return 'Hello, Generator!'
}
const generator = generatorFunction()
for (const value of generator) {
  console.log(value)
}

输出结果如下:

javascript

复制代码

OneTwoThree

扩展运算符也可用于将生成器的值分配给数组:

javascript

复制代码

const values = [...generator]

console.log(values)

输出结果如下:

javascript

复制代码

 ['One', 'Two', 'Three']

可以看到,扩展运算符和 for...of 都不会将 return 的值计入 value

注意:虽然这两种方法对于有限生成器都是有效的,但如果生成器正在处理无限数据流,则无法在不创建无限循环的情况下直接使用扩展运算符或 for...of

我们还可以从迭代结果中解构值:

javascript

复制代码

const [a, b, c]= generator;
console.log(a);
console.log(b);
console.log(c);

输出结果如下:

javascript

复制代码

OneTwoThree

(5)关闭生成器

如我们所见,生成器可以通过遍历其所有值将其 done 属性设置为 true 并将其状态设置为 closed 。除此之外,还有两种方法可以立即关闭生成器:使用 return() 方法和使用 throw() 方法。

使用 return(),生成器可以在任何时候终止,就像在函数体中的 return 语句一样。可以将参数传递给 return(),或将其留空以表示未定义的值。

下面来创建一个具有 yield 值但在函数定义中没有 return 的生成器:

javascript

复制代码

function* generatorFunction() {
  yield'One'  yield'Two'  yield'Three'}const generator = generatorFunction()

第一个 next() 将返回“One”,并将 done 设置为 false。 如果在那之后立即在生成器对象上调用 return() 方法,将获得传递的值并将 done 设置为 true。 对 next() 的任何额外调用都会给出默认的已完成生成器响应,其中包含一个 undefined 值。

javascript

复制代码

generator.next()
generator.return('Return!')
generator.next()
输出结果如下:
javascript
复制代码
{value: "Neo", done: false}
{value: "Return!", done: true}
{value: undefined, done: true}

return() 方法会强制生成器对象完成并忽略任何其他 yield 关键字。 当需要使函数可取消时,这在异步编程中特别有用,例如当用户想要执行不同的操作时中断数据请求,因为无法直接取消 Promise。

如果生成器函数的主体有捕获和处理错误的方法,则可以使用 throw() 方法将错误抛出到生成器中。这将启动生成器,抛出错误并终止生成器。

下面来在生成器函数体内放一个 try...catch 并在发现错误时记录错误:

javascript

复制代码

function* generatorFunction() {
  try {
    yield 'One'
    yield 'Two'
  } catch (error) {
    console.log(error)
  }
}
const generator = generatorFunction()

现在来运行 next() 方法,然后运行 throw() 方法:

javascript

复制代码

generator.next()generator.throw(newError('Error!'))

输出结果如下:

javascript

复制代码

{value: "One", done: false}Error: Error{value: undefined, done: true}

使用 throw() 可以将错误注入到生成器中,该错误被 try...catch 捕获并记录到控制台。

(6)生成器对象方法和状态

下面是生成器对象的方法

  • next():返回生成器中的后面的值;
  • return():在生成器中返回一个值并结束生成器;
  • throw():抛出错误并结束生成器。

下面是生成器对象的状态

  • suspended:生成器已停止执行但尚未终止。
  • closed:生成器因遇到错误、返回或遍历所有值而终止。

(7)yield 委托

除了常规的 yield 运算符之外,生成器还可以使用 yield* 表达式将更多值委托给另一个生成器。当在生成器中遇到 yield* 时,它将进入委托生成器并开始遍历所有 yield 直到该生成器关闭。这可以用于分离不同的生成器函数以在语义上组织代码,同时仍然让它们的所有 **yield** 都可以按正确的顺序迭代。

下面来创建两个生成器函数,其中一个将对另一个进行 yield* 操作:

javascript

复制代码

function* delegate() {
  yield 3
  yield 4
}
function* begin() {
  yield 1
  yield 2
  yield* delegate()
}

接下来,遍历 begin() 生成器函数:

javascript

复制代码

const generator = begin()
for (const value of generator) {
  console.log(value)
}

输出结果如下:

javascript

复制代码

1234

外部的生成器(begin)生成值 1 和 2,然后使用 yield* 委托给另一个生成器(delegate),返回 3 和 4。

yield* 还可以委托给任何可迭代的对象,例如 Array 或 Map。 yield 委托有助于组织代码,因为生成器中任何想要使用 yield 的函数也必须是一个生成器。

(8)在生成器中传递值

上面的例子中,我们使用生成器作为迭代器,并且在每次迭代中产生值。 除了产生值之外,生成器还可以使用 next() 中的值。在这种情况下,yield 将包含一个值。

需要注意,调用的第一个 next() 不会传递值,而只会启动生成器。为了证明这一点,可以记录 yield 的值并使用一些值调用 next() 几次。

javascript

复制代码

function* generatorFunction() {
  console.log(yield)
  console.log(yield)
  return 'End'
}
const generator = generatorFunction()
generator.next()
generator.next(100)
generator.next(200)

输出结果如下:

javascript

复制代码

100200{value: "End", done: true}

除此之外,也可以为生成器提供初始值。下面来创建一个 for 循环并将每个值传递给 next() 方法,同时将一个参数传递给 inital 函数:

javascript

复制代码

function* generatorFunction(value) {
  while (true) {
    value = yield value * 10
  }
}
const generator = generatorFunction(0)
for (let i = 0; i < 5; i++) {
  console.log(generator.next(i).value)
}

这将从 next() 中检索值并为下一次迭代生成一个新值,该值是前一个值乘以 10。 输出结果如下:

javascript

复制代码

010203040

处理启动生成器的另一种方法是将生成器包装在一个函数中,该函数将会在执行任何其他操作之前调用 next() 一次。

(9)async/await

async/await 使处理异步数据更简单、更容易理解。生成器具有比异步函数更广泛的功能,但能够复制类似的行为。以这种方式实现异步编程可以增加代码的灵活性。

下面来构建一个异步函数,它使用 Fetch API 获取数据并将响应记录到控制台。

首先定义一个名为 getUsers 的异步函数,该函数从 API 获取数据并返回一个对象数组,然后调用 getUsers

javascript

复制代码

const getUsers = asyncfunction () {

const response = await fetch('https://jsonplaceholder.typicode.com/users')
  const json = await response.json()
  return json
}
getUsers().then((response) => console.log(response))

输出结果如下:

123.webp.jpg使用生成器可以创建几乎相同但不使用 async/await 关键字的效果。 相反,它将使用我们创建的新函数,并产生值而不是等待 Promise。

javascript

复制代码

const getUsers = asyncAlt(function* () {
  const response = yield fetch('https://jsonplaceholder.typicode.com/users')
  const json = yield response.json()
  return json
})
getUsers().then((response) => console.log(response))

如我们所见,它看起来与 async/await 实现几乎相同,除了有一个生成器函数被传入以产生值。

现在可以创建一个类似于异步函数的 asyncAlt 函数。asyncAlt 有一个 generatorFunction 参数,它是产生 fetch 返回的 Promise 的函数。asyncAlt 返回函数本身,并 resolve 它得到的每个 Promise,直到最后一个:

javascript

复制代码

function asyncAlt(generatorFunction) {
  return function () {
    // 创建并分配生成器对象
    const generator = generatorFunction()
    // 定义一个接受生成器下一次迭代的函数
    function resolve(next) {
      // 如果生成器关闭并且没有更多的值可以生成,则解析最后一个值
      if (next.done) {
        return Promise.resolve(next.value)
      }
      // 如果仍有值可以产生,那么它们就是Promise,必须 resolved。
      return Promise.resolve(next.value).then((response) => {
        return resolve(generator.next(response))
      })
    }
    // 开始 resolve Promise
    return resolve(generator.next())
  }
}

这样就会得到和async/await一样的结果:

尽管这个方法可以为代码增加灵活性,但通常 async/await 是更好的选择,因为它抽象了实现细节并让开发者专注于编写高效代码。

(10)使用场景

很多开发人员认为生成器函数视为一种奇特的 JavaScript 功能,在现实中几乎没有应用。在大多数情况下,确实用不到生成器。

生成器的优点:

  • 惰性求值:除非需要,否则不计算值。 它提供按需计算。只有需要它时,value 才会存在。
  • 内存效率高:由于惰性求值,生成器的内存效率非常高,因为它不会为预先生成的未使用值分配不必要的内存位置。
  • 更简洁的代码:生成器提供更简洁的代码,尤其是在异步行为中。

生成器在对性能要求高的场景中有很大的用处。特别是,它们适用于以下场景:

  • 处理大文件和数据集。
  • 生成无限的数据序列。
  • 按需计算昂贵的逻辑。

Redux sagas 就是实践中使用的生成器的一个很好的例子。它是一个用于管理redux应用异步操作的中间件,redux-saga 通过创建 sagas 将所有异步操作逻辑收集在一个地方集中处理,可以用来代替 redux-thunk 中间件。

2. 异步生成器

ECMAScript 2018 中引入了异步生成器的概念,它是一种特殊类型的异步函数,可以随意停止和恢复其执行。

同步生成器函数和异步生成器函数的区别在于,后者从迭代器对象返回一个异步的、基于 Promise 的结果。

要想创建异步生成器函数,需要声明一个带有星号 * 的生成器函数,前缀为 async

javascript

复制代码

asyncfunction* asyncGenerator() {

}

一旦进入函数,就可以使用 yield 来暂停执行:

javascript

复制代码

asyncfunction* asyncGenerator() {
  yield'One'  yield'Two'}

这里 yield 会暂停执行并返回一个迭代器对象给调用者。这个对象既是可迭代对象,又是迭代器。

异步生成器函数不会像常规函数那样在一步中计算出所有结果。相反,它会逐步提取值。我们可以使用两种方法从异步生成器解析 Promise:

  • 在迭代器对象上调用 next()
  • 使用 for await...of 异步迭代。

对于上面的例子,可以这样做:

javascript

复制代码

async function* asyncGenerator() {
  yield 'One';
  yield 'Two';
}
const go = asyncGenerator();
go.next().then(iterator => console.log(iterator.value));
go.next().then(iterator => console.log(iterator.value));

输出结果如下:

javascript

复制代码

'One';
'Two'

另一种方法使用异步迭代 for await...of。要使用异步迭代,需要用 async 函数包装它:

async function* asyncGenerator() {
  yield 'One';
  yield 'Two';
}
async function consumer() {
  for await (const value of asyncGenerator()) {
    console.log(value);
  }
}
consumer();

for await...of 非常适合提取非有限数据流。

相关文章
|
3月前
|
存储 JavaScript 前端开发
掌握JavaScript中的迭代器和生成器(上)
掌握JavaScript中的迭代器和生成器
|
8月前
|
JavaScript 前端开发 API
深入解析JavaScript Generator 生成器的概念及应用场景
本文讲解了JS生成器的概念和应用场景。生成器是一个可以暂停和恢复执行的函数。利用生成器我们可以很方便地实现自定义的可迭代对象、状态机、惰性计算等,并且还能用它来简化我们的异步操作代码。
229 0
|
4月前
|
前端开发 JavaScript
使用 JavaScript 和 CSS 的随机颜色生成器
使用 JavaScript 和 CSS 的随机颜色生成器
69 0
|
4月前
|
前端开发 JavaScript 数据安全/隐私保护
使用 HTML、CSS 和 JavaScript 制作的随机密码生成器
使用 HTML、CSS 和 JavaScript 制作的随机密码生成器
85 0
|
6月前
|
JavaScript 前端开发
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(1)
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(1)
|
6月前
|
JavaScript 前端开发
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(2)
带你读《现代Javascript高级教程》十五、Iterator 迭代器:简化集合遍历的利器(2)
|
9月前
|
JavaScript 前端开发
《现代Javascript高级教程》Iterator迭代器:简化集合遍历的利器
Iterator 迭代器:简化集合遍历的利器 引言 在 JavaScript 中,迭代器(Iterator)是一种用于遍历集合的接口。迭代器提供了一种统一的方式来访问集合中的元素,无论集合的类型和内部结构如何。通过使用迭代器,我们可以轻松地遍历数组、对象、Map、Set 等各种数据结构,并进行相应的操作。本文将详细介绍迭代器的概念、属性、应用场景,并提供相关的代码示例。
70 1
|
9月前
|
人工智能 前端开发 JavaScript
JavaScript自定义迭代器和提前终止迭代器
与 Iterable 接口类似,任何实现 Iterator 接口的对象都可以作为迭代器使用。下面这个例子中 的 Counter 类只能被迭代一定的次数
69 0
|
9月前
|
前端开发 JavaScript IDE
JavaScript的文档生成器
大多数 IDE 包含主语言的文档生成器。因为 JavaScript 没有官方 IDE,所以过去文档要么手动生成, 要么借用其他语言的文档生成器生成。不过,目前已出现了一些面向 JavaScript 的文档生成器。
198 0
|
前端开发 JavaScript 容器
JavaScript 中的生成器函数
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器对象。在生成器函数内部可以通过yield关键字来中断代码的执行,迭代器每次执行都会执行到下一次 yield 或者 return。
74 0