Generator的正确打开方式
前两年大量的在写
Generator
+co
,用它来写一些类似同步的代码
但实际上,Generator
并不是被造出来干这个使的,不然也就不会有后来的async
、await
了Generator
是一个可以被暂停的函数,并且何时恢复,由调用方决定
希望本文可以帮助你理解Generator
究竟是什么,以及怎么用
放一张图来表示我对Generator
的理解:
一个咖啡机,虽说我并不喝咖啡,可惜找不到造王老吉的机器-.-
我所理解的Generator咖啡机大概就是这么的一个样子的:
- 首先,我们往机器里边放一些咖啡豆
- 等我们想喝咖啡的时候,就可以按开关(gen.next()),机器开始磨咖啡豆、煮咖啡、接下来就得到咖啡了
- 等接满了一杯咖啡后,阀门就会自动关闭(yield)
- 如果你一开始往机器里边放的咖啡豆很多的话,此时,机器里边还是会有一些剩余的,下次再想喝还可以继续按开关,执行(磨豆、煮咖啡、接咖啡)这一套操作
拿Generator
将上述咖啡机实现一下:
function * coffeeMachineGenerator (beans) {
do {
yield cookCoffee()
} while (--beans) // 煮咖啡
function cookCoffee () {
console.log('cooking') return 'Here you are'
}
} // 往咖啡机放咖啡豆
let coffeeMachine = coffeeMachineGenerator(10) // 我想喝咖啡了
coffeeMachine.next() // 我在3秒后还会喝咖啡
setTimeout(() => {
coffeeMachine.next()
}, 3 * 1e3)
代码运行后,我们首先会得到一条cooking
的log
,
然后在3s
后会再次得到一条log
。
这就解释了Generator
是什么:
一个可以暂停的迭代器
调用next
来获取数据(我们自己来决定是否何时煮咖啡)
在遇到yield
以后函数的执行就会停止(接满了一杯,阀门关闭)
我们来决定何时运行剩余的代码next
(什么时候想喝了再去煮)
这是Generator
中最重要的特性,我们只有在真正需要的时候才获取下一个值,而不是一次性获取所有的值
Generator的语法
声明Generator
函数有很多种途径,最重要的一点就是,在function
关键字后添加一个*
function * generator () {}
function* generator () {}
function *generator () {} let generator = function * () {}
let generator = function* () {}
let generator = function *() {} // 错误的示例
let generator = *() => {}
let generator = ()* => {}
let generator = (*) => {}
或者,因为是一个函数,也可以作为一个对象的属性来存在:
class MyClass {
* generator() {}
*generator2() {}
} const obj = {
*generator() {}
* generator() {}
}
generator的初始化与复用
一个Generator
函数通过调用两次方法,将会生成两个完全独立的状态机
所以,保存当前的Generator
对象很重要:
function * generator (name = 'unknown') {
yield `Your name: ${name}`
} const gen1 = generator()
const gen2 = generator('Niko Bellic') gen1.next() // { value: Your name: unknown , done: false}
gen2.next() // { value: Your name: Niko Bellic, done: false}
Method: next()
最常用的next()
方法,无论何时调用它,都会得到下一次输出的返回对象(在代码执行完后的调用将会始终返回{value: undefined, done: true}
)。
next
总会返回一个对象,包含两个属性值:value
:yield
关键字后边表达式的值done
:如果已经没有yield
关键字了,则会返回true
.
function * generator () {
yield 5
return 6
} const gen = generator() console.log(gen.next()) // {value: 5, done: false}
console.log(gen.next()) // {value: 6, done: true}
console.log(gen.next()) // {value: undefined, done: true}
console.log(gen.next()) // {value: undefined, done: true} -- 后续再调用也都会是这个结果
作为迭代器使用
Generator
函数是一个可迭代的,所以,我们可以直接通过for of
来使用它。
function * generator () {
yield 1
yield 2
return 3
} for (let item of generator()) {
item
} //
//
return
不参与迭代
迭代会执行所有的yield
,也就是说,在迭代后的Generator
对象将不会再返回任何有效的值
Method: return()
我们可以在迭代器对象上直接调用return()
,来终止后续的代码执行。
在return
后的所有next()
调用都将返回{value: undefined, done: true}
function * generator () {
yield 1
yield 2
yield 3
} const gen = generator() gen.return() // {value: undefined, done: true}
gen.return('hi') // {value: "hi", done: true}
gen.next() // {value: undefined, done: true}
Method: throw()
在调用throw()
后同样会终止所有的yield
执行,同时会抛出一个异常,需要通过try-catch
来接收:
function * generator () {
yield 1
yield 2
yield 3
} const gen = generator() gen.throw('error text') // Error: error text
gen.next() // {value: undefined, done: true}
Yield的语法
yield
的语法有点像return
,但是,return
是在函数调用结束后返回结果的
并且在调用return
之后不会执行其他任何的操作
function method (a) {
let b = 5
return a + b
// 下边的两句代码永远不会执行
b = 6
return a * b
} method(6) //
method(6) //
而yield的表现则不一样
function * yieldMethod(a) {
let b = 5
yield a + b
// 在执行第二次`next`时,下边两行则会执行
b = 6
return a * b
} const gen = yieldMethod(6)
gen.next().value //
gen.next().value //
yield*
yield*
用来将一个Generator
放到另一个Generator
函数中执行。
有点像[...]
的功能:
function * gen1 () {
yield 2
yield 3
} function * gen2 () {
yield 1
yield * gen1()
yield 4
} let gen = gen2() gen.next().value //
gen.next().value //
gen.next().value //
gen.next().value //
yield的返回值
yield
是可以接收返回值的,返回值可以在后续的代码被使用
一个诡异的写法
function * generator (num) {
return yield yield num
} let gen = generator(1) console.log(gen.next()) // {value: 1, done: false}
console.log(gen.next(2)) // {value: 2, done: false}
console.log(gen.next(3)) // {value: 3, done: true }
我们在调用第一次next
时候,代码执行到了yield num
,此时返回num
然后我们再调用next(2)
,代码执行的是yield (yield num)
,而其中返回的值就是我们在next
中传入的参数了,作为yield num
的返回值存在。
以及最后的next(3)
,执行的是这部分代码return (yield (yield num))
,第二次yield
表达式的返回值。
一些实际的使用场景
上边的所有示例都是建立在已知次数的Generator
函数上的,但如果你需要一个未知次数的Generator
,仅需要创建一个无限循环就够了。
一个简单的随机数生成
比如我们将实现一个随机数的获取:
function * randomGenerator (...randoms) {
let len = randoms.length
while (true) {
yield randoms[Math.floor(Math.random() * len)]
}
} const randomeGen = randomGenerator(1, 2, 3, 4) randomeGen.next().value // 返回一个随机数
代替一些递归的操作
那个最著名的斐波那契数,基本上都会选择使用递归来实现
但是再结合着Generator
以后,就可以使用一个无限循环来实现了:
function * fibonacci(seed1, seed2) {
while (true) {
yield (() => {
seed2 = seed2 + seed1;
seed1 = seed2 - seed1;
return seed2;
})();
}
} const fib = fibonacci(0, 1);
fib.next(); // {value: 1, done: false}
fib.next(); // {value: 2, done: false}
fib.next(); // {value: 3, done: false}
fib.next(); // {value: 5, done: false}
fib.next(); // {value: 8, done: false}
与async/await的结合
再次重申,我个人不认为async/await是Generator的语法糖。。
如果是写前端的童鞋,基本上都会遇到处理分页加载数据的时候
如果结合着Generator
+async
、await
,我们可以这样实现:
async function * loadDataGenerator (url) {
let page = 1 while (true) {
page = (yield await ajax(url, {
data: page
})) || ++page
}
} // 使用setTimeout模拟异步请求
function ajax (url, { data: page }) {
return new Promise((resolve) => {
setTimeout(_ => {
console.log(`get page: ${page}`);
resolve()
}, 1000)
})
} let loadData = loadDataGenerator('get-data-url') await loadData.next()
await loadData.next() // force load page 1
await loadData.next(1)
await loadData.next() // get page: 1
// get page: 2
// get page: 1
// get page: 2
这样我们可以在简单的几行代码中实现一个分页控制函数了。
如果想要从加载特定的页码,直接将page
传入next
即可。
小记
Generator
还有更多的使用方式,(实现异步流程控制、按需进行数据读取)
个人认为,Generator
的优势在于代码的惰性执行,Generator
所实现的事情,我们不使用它也可以做到,只是使用Generator
后,能够让代码的可读性变得更好、流程变得更清晰、更专注于逻辑的实现。
如果有什么不懂的地方 or 文章中一些的错误,欢迎指出
参考资料
- Javascript (ES6) Generators — Part I: Understanding Generators
- What are JavaScript Generators and how to use them
Generator的正确打开方式的更多相关文章
- C++11随机数的正确打开方式
C++11随机数的正确打开方式 在C++11之前,现有的随机数函数都存在一个问题:在利用循环多次获取随机数时,如果程序运行过快或者使用了多线程等方法,srand((unsigned)time(null ...
- iOS开发小技巧--相机相册的正确打开方式
iOS相机相册的正确打开方式- UIImagePickerController 通过指定sourceType来实现打开相册还是相机 UIImagePickerControllerSourceTypeP ...
- Xcode 的正确打开方式——Debugging(转载)
Xcode 的正确打开方式——Debugging 程序员日常开发中有大量时间都会花费在 debug 上,从事 iOS 开发不可避免地需要使用 Xcode.这篇博客就主要介绍了 Xcode 中几种能 ...
- C#语法——泛型的多种应用 C#语法——await与async的正确打开方式 C#线程安全使用(五) C#语法——元组类型 好好耕耘 redis和memcached的区别
C#语法——泛型的多种应用 本篇文章主要介绍泛型的应用. 泛型是.NET Framework 2.0 版类库就已经提供的语法,主要用于提高代码的可重用性.类型安全性和效率. 泛型的定义 下面定义了 ...
- InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式
InnoDB缓冲池预加载在MySQL 5.7中的正确打开方式 https://mp.weixin.qq.com/s/HGa_90XvC22anabiBF8AbQ 在这篇文章里,我将讨论在MySQL 5 ...
- Console控制台的正确打开方式
Console控制台的正确打开方式 console对象提供了访问浏览器调试模式的信息到控制台 -- Console对象 |-- assert() 如果第一个参数断言为false,则在控制台输出错误信息 ...
- 任务队列和异步接口的正确打开方式(.NET Core版本)
任务队列和异步接口的正确打开方式 什么是异步接口? Asynchronous Operations Certain types of operations might require processi ...
- (一)Redis for Windows正确打开方式
目录 (一)Redis for Windows正确打开方式 (二)Redis for 阿里云公网连接 (三)Redis for StackExchange.Redis 下载地址 官网.中文网1 及 中 ...
- List的remove()方法的三种正确打开方式
转: java编程:List的remove()方法的三种正确打开方式! 2018年08月12日 16:26:13 Aries9986 阅读数 2728更多 分类专栏: leetcode刷题 版权声 ...
随机推荐
- JQuery实战总结二 横向纵向菜单下拉效果图
记得以前在浏览了大多数网站的上面发现很多下拉的导航栏,觉得特别好玩,毕竟咱们是学习编程的嘛,对这下拉的效果还是挺感兴趣的,这种淡入淡出,随着鼠标移动的位置不同.有无等而出现不同的效果,给用户以神美感. ...
- struts2 easyui实现datagrid的crud
最近两天因为项目需要,接触了easyui,要用它的datagrid实现crud.第一次做,花了一天时间才完成所有功能,昨天做另外一个模块,同样的功能只用了两个小时. 现在把第一次做datagrid时遇 ...
- JUnit4快速入门
JUnit最佳实践 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class CardServiceTest { /** * 最佳 ...
- OpenCV 实现分水岭算法
种子点的标记没有太搞懂,这个算法的速度还是很快的 // watershed_test20140801.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h&q ...
- Swift之GCD使用指南1
Grand Central Dispatch(GCD)是异步执行任务的技术之一.一般将应用程序中记述的线程管理用的代码在系统级中实现.开发者只需要定义想执行的任务并追加到适当的Dispatch Que ...
- 基于ARM-contexA9按键驱动开发
之前我们写过LED和蜂鸣器的驱动,其实那两个都是一个模版的,因为都是将IO口配置成输出模式,然后用高低电平来驱动这些设备.其实linux设备驱动,说白了跟单片机开发的方式是差不多的,只不过内核的开发基 ...
- Android进阶(十七)AndroidAPP开发问题汇总(一)
首先来看一下猎头公司对于Android方向人才招聘的需求: 猎头公司推荐------资深Java软件工程师(Android方向) 岗位职责: 1.熟悉Java语言,熟悉B/S开发的基本结构 2.能运用 ...
- Android高级控件(四)——VideoView 实现引导页播放视频欢迎效果,超级简单却十分的炫酷
Android高级控件(四)--VideoView 实现引导页播放视频欢迎效果,超级简单却十分的炫酷 是不是感觉QQ空间什么的每次新版本更新那炫炫的引导页就特别的激动,哈哈,其实他实现起来真的很简单很 ...
- UITableView设置单元格选中后只显示一个打勾的三种简单方法(仅供参考)
1.第一种方法:先定位到最后一行,若选中最后一行直接退出,否则用递归改变上次选中的状态,重新设置本次选中的状态. - (UITableViewCell*)tableView:(UITableView* ...
- Hadoop DataNode不能正常工作的原因
在把Hadoop环境搭建成功,并且也Hadoop的各个组件都正常工作.在重启过几次Hadoop后发现DataNode不能正常工作,打开Hadoop 的后台http://localhost:50030和 ...