Generator 函数是 ES6 提供的一种异步编程解决方案,它是一个状态机,封装了多个内部状态;它提供一种有效的方式来制作迭代器,并且能够处理无限数据流;当与Promises一起使用时,生成器可以模拟async/await功能,这使我们能够以更直接和可读的方式处理异步代码。
接下来开始正文
1. Generator 函数
generator
函数返回一个Generator
对象,并且它是通过function
关键字后跟一个星号*
来定义的,如下所示:
// Generator function declaration
function* generatorFunction() {}
当然还有另一种写法
// Generator function declaration
function *generatorFunction() {}
这两种写法产生的效果都是一样的,不过后面这种不太常写。
generator
函数可以在表达式中定义:
// Generator function expression
const generatorFunction = function* () {}
生成器甚至可以是对象或类的方法:
// Generator as the method of an object
const generatorObj = {
*generatorMethod() {},
}
// Generator as the method of a class
class GeneratorClass {
*generatorMethod() {}
}
注意:与常规函数不同,生成器不能使用new关键字构造,也不能与箭头函数结合使用
2. Generator对象
JavaScript中的普通函数会运行到完成状态,调用函数会在到达return关键字时返回一个值。如果return省略关键字,则函数将隐式返回undefined。
如下:
function sum(a, b) {
return a + b
}
const value = sum(5, 6) //11
这种普通的函数会直接返回一个值,但是Generator函数却不会立即返回一个值,而是返回一个可迭代的Generator对象。如下所示:
function* generatorFunction() {
return 'Hello, Generator!'
}
const generator = generatorFunction()
console.log(generator)
输出的结果如图,我们实际上得到的是处于suspended状态的对象
3.next()方法
该generator
函数返回的对象是一个迭代器(iterator Object)。为了输出结果,就需要使用迭代器中next()
方法。该next()方法返回具有value和done属性的对象。value代表返回的值,并done指示迭代器是否已遍历其所有值。
接下来使用next()
方法,看看输出什么
generator.next()
输出结果为:{ value: 'Hello, Generator!', done: true }
这回输出了我们想要的结果
接着我们再看看generator的状态
由于迭代器已运行完成,因此generator对象的状态将从suspended变为closed。
4.yield 表达式
generator
函数内部还引入了一个关键字yield
,yield可以暂停generator
函数并返回yield后的值,从而提供了一种轻量级的方法来遍历值。
接下来看一个例子:
function* generatorFunction() {
yield '1'
yield '2'
yield '3'
return 'end'
}
const generator = generatorFunction()
// 分别调用四次next()
generator.next()
generator.next()
generator.next()
generator.next()
接着看看每次调用的结果
{value: "1", done: false}
{value: "2", done: false}
{value: "3", done: false}
{value: "end", done: true}
5. generator函数的迭代
generator
函数返回一个迭代器,所以它也遵守迭代协议,可以使用for…of…等进行输出。
看下面例子:
function* generatorFunction() {
yield '1'
yield '2'
yield '3'
return 'end'
}
const generator = generatorFunction()
for (const value of generator) {
console.log(value)
}
这个输出结果为: 1 2 3
除了for of ,也可以使用扩展符号(…)进行输出
function* generatorFunction() {
yield '1'
yield '2'
yield '3'
return 'end'
}
const generator = generatorFunction()
const value = [...generator]
console.log(value)
输出结果为: [ ‘1’, ‘2’, ‘3’ ]
虽然这两种方法都适用于generator函数,但是如果generator函数正在处理无限数据流时,则这两种方法使用便受限了。
6. 停止generator函数
上面讲的如何执行generator函数,接下来介绍如何终止generator函数:即使用return()方法和throw()方法
6.1 return
使用return(),可以在任何时候终止生成器,就像return在函数体内有一条语句一样。您可以将参数传递到中return(),也可以将其保留为空白以获取未定义的值。
function* generatorFunction() {
yield 'a'
yield 'b'
yield 'c'
}
const generator = generatorFunction()
generator.next()
generator.return('end!!')
generator.next()
输出结果为
{value: "a", done: false}
{value: "end!!", done: true}
{value: undefined, done: true}
再执行完return之后,generator对象的状态就变为closed了
generatorFunction {<closed>}
__proto__: Generator
[[GeneratorLocation]]: VM36:1
[[GeneratorStatus]]: "closed"
[[GeneratorFunction]]: ƒ* generatorFunction()
[[GeneratorReceiver]]: Window
该return()方法强制Generator对象完成并且忽略任何其他yield关键字,这在异步编程中特别有用,例如当用户想要执行其他操作而需要中断Web请求时,我们无法直接取消Promise,所以可以使用这种方法。
6.2 throw
使用throw方法是利用try…catch…会捕获错误并处理错误的这种能力,详见下面例子:
function* generatorFunction() {
try {
yield 'a'
yield 'b'
} catch (error) {
console.log(error)
}
}
const generator = generatorFunction()
generator.next()
generator.throw(new Error('ERROR!'))
输出结果为:
{value: 'a', done: false}
Error:ERROR!
{value: undefined, done: true}
使用return和throw都是可以实现中断generator函数的。
7. 小结
在generator函数中,可以使用三种方法:
- next() :返回迭代器中的下一个值
- return():中断迭代器并返回一个值
- throw():中断迭代器并返回报错信息
在generator对象中,有两种状态“
- suspended:迭代器已暂停执行但尚未终止
- closed:迭代器因遇到错误,返回或遍历所有值而终止