博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Generator的正确打开方式
阅读量:7075 次
发布时间:2019-06-28

本文共 5788 字,大约阅读时间需要 19 分钟。

前两年大量的在写Generator+co,用它来写一些类似同步的代码

但实际上,Generator并不是被造出来干这个使的,不然也就不会有后来的asyncawait
Generator是一个可以被暂停的函数,并且何时恢复,由调用方决定
希望本文可以帮助你理解Generator究竟是什么,以及怎么用

放一张图来表示我对Generator的理解:

一个咖啡机,虽说我并不喝咖啡,可惜找不到造王老吉的机器-.-

我所理解的Generator咖啡机大概就是这么的一个样子的:

  1. 首先,我们往机器里边放一些咖啡豆
  2. 等我们想喝咖啡的时候,就可以按开关(gen.next()),机器开始磨咖啡豆、煮咖啡、接下来就得到咖啡了
  3. 等接满了一杯咖啡后,阀门就会自动关闭(yield)
  4. 如果你一开始往机器里边放的咖啡豆很多的话,此时,机器里边还是会有一些剩余的,下次再想喝还可以继续按开关,执行(磨豆、煮咖啡、接咖啡)这一套操作

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)

 

代码运行后,我们首先会得到一条cookinglog

然后在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总会返回一个对象,包含两个属性值:

valueyield关键字后边表达式的值
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}// 1// 2

 

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 textgen.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) // 11method(6) // 11

 

而yield的表现则不一样

function * yieldMethod(a) {  let b = 5  yield a + b  // 在执行第二次`next`时,下边两行则会执行  b = 6  return a * b}const gen = yieldMethod(6)gen.next().value // 11gen.next().value // 36

 

yield*

yield*用来将一个Generator放到另一个Generator函数中执行。

有点像[...]的功能:

function * gen1 () {  yield 2  yield 3}function * gen2 () {  yield 1  yield * gen1()  yield 4}let gen = gen2()gen.next().value // 1gen.next().value // 2gen.next().value // 3gen.next().value // 4

 

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+asyncawait,我们可以这样实现:

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 1await 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 文章中一些的错误,欢迎指出

参考资料

转载地址:http://ukuml.baihongyu.com/

你可能感兴趣的文章
IDF2013:大数据带来医疗行业转折点
查看>>
Windows Server 2008服务器系统的远程桌面连接数量
查看>>
数据分析后遗症:大数据互联网隐私之殇
查看>>
IBM“绿色地平线”为中网加油 大数据技术助力全民健身
查看>>
2017杭州云栖大会—移动云专场【赠票】
查看>>
大数据拼精准 可否触动电商个性营销神经
查看>>
互联网时代 数据中心如何满足未来需求
查看>>
目标检测101:一文带你读懂深度学习框架下的目标检测
查看>>
《区块链原理、设计与应用》一3.1 应用场景概览
查看>>
吴晓军:加强保险业大数据能力的建设
查看>>
美光Sun合作长寿命SLC闪存 100万次写入
查看>>
OCZ新Summit系列固态硬盘强悍性能曝光
查看>>
视频|每日CeBIT总结:中国元素充斥 多元化工业展也Fashion
查看>>
《中国人工智能学会通讯》——1.44 到底什么是虹膜识别
查看>>
存储“芯”突破口 解析3D NADN产业竞争形势
查看>>
用MAID 2.0降低存储费用
查看>>
积极推动中国金融资产市场化流动
查看>>
《中国人工智能学会通讯》——2.15 手术机器人
查看>>
旗舰店揭幕大疆无人机用阿里云全球直播
查看>>
客观看待社保系统管理漏洞
查看>>