Generator的正确打开方式
前两年大量的在写
Generator+co,用它来写一些类似同步的代码
但实际上,Generator并不是被造出来干这个使的,不然也就不会有后来的async、await了Generator是一个可以被暂停的函数,并且何时恢复,由调用方决定
希望本文可以帮助你理解Generator究竟是什么,以及怎么用
放一张图来表示我对Generator的理解:
一个咖啡机,虽说我并不喝咖啡,可惜找不到造王老吉的机器-.-
我所理解的Generator咖啡机大概就是这么的一个样子的:
- 首先,我们往机器里边放一些咖啡豆
- 等我们想喝咖啡的时候,就可以按开关(
gen.next()),机器开始磨咖啡豆、煮咖啡、接下来就得到咖啡了 - 等接满了一杯咖啡后,阀门就会自动关闭(
yield) - 如果你一开始往机器里边放的咖啡豆很多的话,此时,机器里边还是会有一些剩余的,下次再想喝还可以继续按开关,执行(磨豆、煮咖啡、接咖啡)这一套操作
拿Generator将上述咖啡机实现一下:
1 | function * coffeeMachineGenerator (beans) { |
代码运行后,我们首先会得到一条cooking的log,
然后在3s后会再次得到一条log。
这就解释了Generator是什么:
一个可以暂停的迭代器
调用next来获取数据(我们自己来决定是否何时煮咖啡)
在遇到yield以后函数的执行就会停止(接满了一杯,阀门关闭)
我们来决定何时运行剩余的代码next(什么时候想喝了再去煮)
这是Generator中最重要的特性,我们只有在真正需要的时候才获取下一个值,而不是一次性获取所有的值
Generator的语法
声明Generator函数有很多种途径,最重要的一点就是,在function关键字后添加一个*
1 | function * generator () {} |
或者,因为是一个函数,也可以作为一个对象的属性来存在:
1 | class MyClass { |
generator的初始化与复用
一个Generator函数通过调用两次方法,将会生成两个完全独立的状态机
所以,保存当前的Generator对象很重要:
1 | function * generator (name = 'unknown') { |
Method: next()
最常用的next()方法,无论何时调用它,都会得到下一次输出的返回对象(在代码执行完后的调用将会始终返回{value: undefined, done: true})。
next总会返回一个对象,包含两个属性值:value:yield关键字后边表达式的值done :如果已经没有yield关键字了,则会返回true .
1 | function * generator () { |
作为迭代器使用
Generator函数是一个可迭代的,所以,我们可以直接通过for of来使用它。
1 | function * generator () { |
return不参与迭代
迭代会执行所有的yield,也就是说,在迭代后的Generator对象将不会再返回任何有效的值
Method: return()
我们可以在迭代器对象上直接调用return(),来终止后续的代码执行。
在return后的所有next()调用都将返回{value: undefined, done: true}
1 | function * generator () { |
Method: throw()
在调用throw()后同样会终止所有的yield执行,同时会抛出一个异常,需要通过try-catch来接收:
1 | function * generator () { |
Yield的语法
yield的语法有点像return,但是,return是在函数调用结束后返回结果的
并且在调用return之后不会执行其他任何的操作
1 | function method (a) { |
而yield的表现则不一样
1 | function * yieldMethod(a) { |
yield*
yield*用来将一个Generator放到另一个Generator函数中执行。
有点像[...]的功能:
1 | function * gen1 () { |
yield的返回值
yield是可以接收返回值的,返回值可以在后续的代码被使用
一个诡异的写法
1 | function * generator (num) { |
我们在调用第一次next时候,代码执行到了yield num,此时返回num
然后我们再调用next(2),代码执行的是yield (yield num),而其中返回的值就是我们在next中传入的参数了,作为yield num的返回值存在。
以及最后的next(3),执行的是这部分代码return (yield (yield num)),第二次yield表达式的返回值。
一些实际的使用场景
上边的所有示例都是建立在已知次数的Generator函数上的,但如果你需要一个未知次数的Generator,仅需要创建一个无限循环就够了。
一个简单的随机数生成
比如我们将实现一个随机数的获取:
1 | function * randomGenerator (...randoms) { |
代替一些递归的操作
那个最著名的斐波那契数,基本上都会选择使用递归来实现
但是再结合着Generator以后,就可以使用一个无限循环来实现了:
1 | function * fibonacci(seed1, seed2) { |
与async/await的结合
再次重申,我个人不认为async/await是Generator的语法糖。。
如果是写前端的童鞋,基本上都会遇到处理分页加载数据的时候
如果结合着Generator+async、await,我们可以这样实现:
1 | async function * loadDataGenerator (url) { |
这样我们可以在简单的几行代码中实现一个分页控制函数了。
如果想要从加载特定的页码,直接将page传入next即可。
小记
Generator还有更多的使用方式,(实现异步流程控制、按需进行数据读取)
个人认为,Generator的优势在于代码的惰性执行,Generator所实现的事情,我们不使用它也可以做到,只是使用Generator后,能够让代码的可读性变得更好、流程变得更清晰、更专注于逻辑的实现。
如果有什么不懂的地方 or 文章中一些的错误,欢迎指出