作者选择了 开放式互联网 / 自由言论基金作为 写给捐赠计划的一部分接受捐款。
介绍
在 ECMAScript 2015中,引入了 JavaScript 语言的生成器。 generator 是一个可以暂停和恢复的过程,可以产生多个值。
发电机可以维持状态,提供一种高效的方式来制造迭代器,并且能够处理无限数据流,可用于在 Web 应用程序的前端实现无限滚动,以声音波数据操作等等。此外,当与 Promises使用时,发电机可以模仿async/await
功能,这使我们能够以更简单和可读的方式处理 asynchronous code 。
在本文中,我们将涵盖如何创建发电机函数,如何重复发电机
对象,发电机内部的收益
和回报
之间的差异,以及与发电机工作的其他方面。
发电机功能
生成函数是返回一个生成
对象的函数,并由函数
的关键字,然后是星座(*
),如下所示:
1// Generator function declaration
2function* generatorFunction() {}
有时,您会看到函数名称旁边的星座,而不是函数关键字,例如函数 *generatorFunction()
。
生成器函数也可以在一个表达式中定义,例如常规 函数:
1// Generator function expression
2const generatorFunction = function*() {}
1// Generator as the method of an object
2const generatorObj = {
3 *generatorMethod() {},
4}
5
6// Generator as the method of a class
7class GeneratorClass {
8 *generatorMethod() {}
9}
本文中的示例将使用生成器函数声明语法。
<$>[注]
注:与常规函数不同,发电器不能用新
关键字构建,也不能与 箭头函数一起使用。
现在你知道如何声明发电机函数,让我们看看它们返回的可重复的发电机
对象。
物体发电机
传统上,JavaScript中的函数运行到完成,呼叫函数将返回值,当它到达返回
关键字时。
例如,在下面的代码中,我们声明一个‘sum()’函数,该函数返回一个值,该值是两个整数参数的总和:
1// A regular function that sums two values
2function sum(a, b) {
3 return a + b
4}
调用函数返回一个值,该值是参数的总和:
1sum(5, 6) // 11
然而,生成函数不会立即返回值,而是返回可迭代的生成
对象。
1// Declare a generator function with a single return value
2function* generatorFunction() {
3 return 'Hello, Generator!'
4}
当我们调用发电函数时,它会返回 Generator
对象,我们可以将其分配给一个变量:
1// Assign the Generator object to generator
2const generator = generatorFunction();
如果这是一个常规的函数,我们会期望发电机
给我们函数中返回的字符串,但是,我们实际上得到的是处于暂停
状态的对象,因此呼叫发电机
会产生类似于以下的输出:
1[secondary_label Output]
2generatorFunction {<suspended>}
3 __proto__: Generator
4 [[GeneratorLocation]]: VM272:1
5 [[GeneratorStatus]]: "suspended"
6 [[GeneratorFunction]]: ƒ* generatorFunction()
7 [[GeneratorReceiver]]: Window
8 [[Scopes]]: Scopes[3]
函数返回的生成器
对象是 iterator。 iterator 是具有可用的next()
方法的对象,用于通过值序列进行迭代。
知道这一点,让我们在我们的发电机
上呼叫next()
,并获得迭代器的当前值和状态:
1// Call the next method on the Generator object
2generator.next()
这将产生以下产出:
1[secondary_label Output]
2{value: "Hello, Generator!", done: true}
调用next()
时返回的值为Hello, Generator!
和完成
状态为true
,因为这个值来自关闭 iterator 的返回
。
1[secondary_label Output]
2generatorFunction {<closed>}
截至目前,我们只展示了发电机函数是如何获得函数的回报
值的更复杂方法,但发电机函数也有独特的特性,使其与正常函数区分开来。
盈利
运营商
发电机在JavaScript中引入一个新的关键字: yield
。 yield
可以暂停发电机函数并返回随后的值 yield
,提供轻量级的方式来迭代值。
在本示例中,我们将将发电机函数暂停三次,以不同的值,然后在末尾返回一个值,然后我们将我们的发电机
对象分配给发电机
变量。
1// Create a generator function with multiple yields
2function* generatorFunction() {
3 yield 'Neo'
4 yield 'Morpheus'
5 yield 'Trinity'
6
7 return 'The Oracle'
8}
9
10const generator = generatorFunction()
现在,当我们在发电机函数上调用‘next()’时,它每次遇到‘yield’时都会停下来。‘done’将在每个‘yield’后设置为‘false’,表示发电机尚未完成。
使用next()
方法连续四次:
1// Call next four times
2generator.next()
3generator.next()
4generator.next()
5generator.next()
这些将为以下四个输出线提供顺序:
1[secondary_label Output]
2{value: "Neo", done: false}
3{value: "Morpheus", done: false}
4{value: "Trinity", done: false}
5{value: "The Oracle", done: true}
请注意,发电机不需要返回
;如果被忽略,最后一次迭代将返回{值:未定义,完成: true}
,以及发电机完成后任何后续调用next()
。
超越发电机
使用‘next()’方法,我们通过‘Generator’对象手动迭代,接收了整个对象的所有‘值’和‘完成’属性,然而,就像 ‘Array’, ‘Map’和‘Set’一样,一个‘Generator’遵循 iteration protocol,并且可以通过 `for...of进行迭代:
1// Iterate over Generator object
2for (const value of generator) {
3 console.log(value)
4}
这将返回如下:
1[secondary_label Output]
2Neo
3Morpheus
4Trinity
散布运算器也可以用来分配一个数组的生成器
的值。
1// Create an array from the values of a Generator object
2const values = [...generator]
3
4console.log(values)
这将为下列数组提供:
1[secondary_label Output]
2(3) ["Neo", "Morpheus", "Trinity"]
散布和为...of
都不会将回报
纳入值中(在这种情况下,它将是The Oracle
)。
<$>[注]
注:虽然这两种方法在与有限生成器工作时都有效,但如果生成器处理的是无限数据流,则无法直接使用 Spread 或for...of
而无需创建无限循环。
关闭发电机
正如我们所看到的,发电机可以将其完成
属性设置为真实
并将其状态设置为关闭
,通过重复其所有值。
使用return()
,生成器可以随时终止,就像一个return
语句存在于函数体内一样。
要演示 return()
,我们将创建一个产生器,在函数定义中有几个 yield
值,但没有 return
:
1function* generatorFunction() {
2 yield 'Neo'
3 yield 'Morpheus'
4 yield 'Trinity'
5}
6
7const generator = generatorFunction()
第一個「next()」將給我們「Neo」,而「完成」設定為「false」。如果我們在「Generator」對象上立即召喚一個「return()」方法,我們現在就會得到通過的值,並且「完成」設定為「true」。
要证明这一点,在发电机
上运行以下三种方法:
1generator.next()
2generator.return('There is no spoon!')
3generator.next()
这将带来以下三个结果:
1[secondary_label Output]
2{value: "Neo", done: false}
3{value: "There is no spoon!", done: true}
4{value: undefined, done: true}
返回()
方法迫使生成器
对象完成并忽略任何其他输出
关键字,这在非同步编程中尤其有用,当您需要使函数可以取消时,例如当用户想要执行不同的操作时中断网页请求,因为无法直接取消承诺。
如果发电机函数的身体有办法捕捉和处理错误,你可以使用投( )
方法将错误扔进发电机,从而启动发电机,扔进错误,并终止发电机。
为了证明这一点,我们将把一个 try...catch
放在发电机函数体内,如果找到一个错误,则会登录一个错误:
1// Define a generator function with a try...catch
2function* generatorFunction() {
3 try {
4 yield 'Neo'
5 yield 'Morpheus'
6 } catch (error) {
7 console.log(error)
8 }
9}
10
11// Invoke the generator and throw an error
12const generator = generatorFunction()
现在,我们将运行next()
方法,然后是throw()
:
1generator.next()
2generator.throw(new Error('Agent Smith!'))
这将产生以下产出:
1[secondary_label Output]
2{value: "Neo", done: false}
3Error: Agent Smith!
4{value: undefined, done: true}
使用投( )
,我们将错误注入到发电机中,该错误被试...捕获
捕获并登录到控制台。
发电机对象方法和状态
下表显示了可以用于生成器
对象的方法列表:
Method | Description |
---|---|
next() | Returns the next value in a generator |
return() | Returns a value in a generator and finishes the generator |
throw() | Throws an error and finishes the generator |
下表列出了一个生成器
对象的可能状态:
Status | Description |
---|---|
suspended | Generator has halted execution but has not terminated |
closed | Generator has terminated by either encountering an error, returning, or iterating through all values |
代表团获利
除了正常的输出
运算器外,发电机还可以使用输出
表达式(LINK0
)将进一步的值转移到另一个发电机上。当输出
在发电机内遇到时,它会进入发电机内,并开始通过所有输出进行迭代,直到发电机关闭。
为了示范,我们可以创建两个发电机函数,其中一个将yield*
在另一个操作:
1// Generator function that will be delegated to
2function* delegate() {
3 yield 3
4 yield 4
5}
6
7// Outer generator function
8function* begin() {
9 yield 1
10 yield 2
11 yield* delegate()
12}
接下来,让我们通过 begin()
生成函数进行迭代:
1// Iterate through the outer generator
2const generator = begin()
3
4for (const value of generator) {
5 console.log(value)
6}
这将以它们生成的顺序提供以下值:
1[secondary_label Output]
21
32
43
54
外部发电机输出值1
和2
,然后转移到另一个发电机输出
,返回3
和4
。
yield*
也可以转移到任何可迭代的对象,例如数组或地图。 yield delegation 可以帮助组织代码,因为在生成器中想要使用yield
的任何函数也必须是生成器。
无限数据流
发电机的一个有用的方面是能够使用无限的数据流和集合,这可以通过在发电机函数中创建无限循环来证明。
在下列代码块中,我们定义这个发电机函数,然后启动发电机:
1// Define a generator function that increments by one
2function* incrementer() {
3 let i = 0
4
5 while (true) {
6 yield i++
7 }
8}
9
10// Initiate the generator
11const counter = incrementer()
现在,通过使用 next()
的值迭代:
1// Iterate through the values
2counter.next()
3counter.next()
4counter.next()
5counter.next()
这将产生以下产出:
1[secondary_label Output]
2{value: 0, done: false}
3{value: 1, done: false}
4{value: 2, done: false}
5{value: 3, done: false}
函数返回无限循环中的连续值,而完成
属性仍然是假
,确保它不会完成。
与发电机,你不必担心创建一个无限循环,因为你可以停止和恢复执行随心所欲. 然而,你仍然需要小心如何调用发电机. 如果你在无限数据流中使用 spread 或 `for...of,你仍然会同时重复一个无限循环,这将导致环境崩溃。
对于一个更复杂的无限数据流示例,我们可以创建一个Fibonacci生成函数。
1// Create a fibonacci generator function
2function* fibonacci() {
3 let prev = 0
4 let next = 1
5
6 yield prev
7 yield next
8
9 // Add previous and next values and yield them forever
10 while (true) {
11 const newVal = next + prev
12
13 yield newVal
14
15 prev = next
16 next = newVal
17 }
18}
为了测试这一点,我们可以循环通过一个有限的数字,并将斐波纳奇序列打印到控制台上。
1// Print the first 10 values of fibonacci
2const fib = fibonacci()
3
4for (let i = 0; i < 10; i++) {
5 console.log(fib.next().value)
6}
这将给出如下:
1[secondary_label Output]
20
31
41
52
63
75
88
913
1021
1134
能够与无限数据集一起工作是使发电机如此强大的一部分,这对于实施无限滚动在Web应用程序前端等实例来说很有用。
在发电机中传输值
在本文中,我们使用发电机作为迭代器,并在每个迭代中产生值. 除了产生值之外,发电机还可以消耗从 next()
的值. 在这种情况下, yield
将包含一个值。
重要的是要注意,呼叫的第一个next()
不会传递一个值,而只会启动发电器。 为了证明这一点,我们可以记录yield
的值,并呼叫next()
一些值。
1function* generatorFunction() {
2 console.log(yield)
3 console.log(yield)
4
5 return 'The end'
6}
7
8const generator = generatorFunction()
9
10generator.next()
11generator.next(100)
12generator.next(200)
这将产生以下产出:
1[secondary_label Output]
2100
3200
4{value: "The end", done: true}
在以下示例中,我们将创建一个for
循环并将每个值传入next()
方法,但还将一个参数传递到初始函数中:
1function* generatorFunction(value) {
2 while (true) {
3 value = yield value * 10
4 }
5}
6
7// Initiate a generator and seed it with an initial value
8const generator = generatorFunction(0)
9
10for (let i = 0; i < 5; i++) {
11 console.log(generator.next(i).value)
12}
我们将从next()
中获取值,并在下一次迭代中产生一个新的值,这是前一个值十倍的值。
1[secondary_label Output]
20
310
420
530
640
处理发电机启动的另一种方法是将发电机包裹到一个函数中,在做其他任何事情之前,它总是会打电话给‘next()’。
async
/ 等待
与发电机
非同步函数是可在 ES6+ JavaScript 中使用的函数类型,通过使其看起来同步来使处理非同步数据更容易理解。发电机具有比非同步函数更广泛的功能,但能够复制类似的行为。
在本节中,我们将展示使用发电机复制 async
/await
的例子。
让我们构建一个非同步函数,该函数使用 Fetch API从 JSONPlaceholder API获取数据(为测试提供示例 JSON数据),并将响应记录到控制台。
首先,定义一个名为getUsers
的非同步函数,该函数从API中获取数据并返回一组对象,然后拨打getUsers
:
1const getUsers = async function() {
2 const response = await fetch('https://jsonplaceholder.typicode.com/users')
3 const json = await response.json()
4
5 return json
6}
7
8// Call the getUsers function and log the response
9getUsers().then(response => console.log(response))
这将提供类似于以下的 JSON 数据:
1[secondary_label Output]
2[ {id: 1, name: "Leanne Graham" ...},
3 {id: 2, name: "Ervin Howell" ...},
4 {id: 3, name": "Clementine Bauch" ...},
5 {id: 4, name: "Patricia Lebsack"...},
6 {id: 5, name: "Chelsey Dietrich"...},
7 ...]
使用发电机,我们可以创建一些几乎相同的东西,而不是使用async
或Wait
的关键字,而不是使用我们创建的新函数和yield
的值,而不是Wait
的承诺。
在下面的代码块中,我们定义了一个名为getUsers
的函数,该函数使用我们新的asyncAlt
函数(我们将在稍后写下)来模仿async
/await
。
1const getUsers = asyncAlt(function*() {
2 const response = yield fetch('https://jsonplaceholder.typicode.com/users')
3 const json = yield response.json()
4
5 return json
6})
7
8// Invoking the function
9getUsers().then(response => console.log(response))
正如我们所看到的,它看起来几乎与async
/ Wait
实现相同,但有一个生成器函数正在传递中,该函数会产生值。
现在我们可以创建一个类似于非同步函数的‘asyncAlt’函数。‘asyncAlt’具有作为参数的生成函数,这是我们的函数,使‘fetch’返回的承诺。
1// Define a function named asyncAlt that takes a generator function as an argument
2function asyncAlt(generatorFunction) {
3 // Return a function
4 return function() {
5 // Create and assign the generator object
6 const generator = generatorFunction()
7
8 // Define a function that accepts the next iteration of the generator
9 function resolve(next) {
10 // If the generator is closed and there are no more values to yield,
11 // resolve the last value
12 if (next.done) {
13 return Promise.resolve(next.value)
14 }
15
16 // If there are still values to yield, they are promises and
17 // must be resolved.
18 return Promise.resolve(next.value).then(response => {
19 return resolve(generator.next(response))
20 })
21 }
22
23 // Begin resolving promises
24 return resolve(generator.next())
25 }
26}
这将提供与async
/await
版本相同的输出:
1[secondary_label Output]
2[ {id: 1, name: "Leanne Graham" ...},
3 {id: 2, name: "Ervin Howell" ...},
4 {id: 3, name": "Clementine Bauch" ...},
5 {id: 4, name: "Patricia Lebsack"...},
6 {id: 5, name: "Chelsey Dietrich"...},
7 ...]
请注意,此实现旨在展示如何将发电机用于代替async
/await
,并且不是一个生产准备的设计,它没有错误处理设置,也不具备将参数传输到产值的能力。
结论
发电机是可以停止和恢复执行的流程. 它们是JavaScript的强大而多功能的功能,虽然它们不经常使用。 在本教程中,我们了解了发电机函数和发电机对象,发电机可用的方法,‘yield’和‘yield*’操作员,以及与有限和无限数据集使用的发电机。
如果您想了解有关JavaScript语法的更多信息,请参阅我们的教程(理解,绑定,调用和应用在JavaScript中)和(理解JavaScript中的地图和设置对象)。