前两年大量的在写
Generator
+co
,用它来写一些相似同步的代码
但实际上,Generator
并非被造出来干这个使的,否则也就不会有后来的async
、await
了
Generator
是一个能够被暂停的函数,而且什么时候恢复,由调用方决定
但愿本文能够帮助你理解Generator
到底是什么,以及怎么用javascript
放一张图来表示我对Generator
的理解:前端
我所理解的Generator咖啡机大概就是这么的一个样子的:java
gen.next()
),机器开始磨咖啡豆、煮咖啡、接下来就获得咖啡了yield
)拿Generator
将上述咖啡机实现一下:git
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
。es6
这就解释了Generator
是什么:
一个能够暂停的迭代器
调用next
来获取数据(咱们本身来决定是否什么时候煮咖啡)
在遇到yield
之后函数的执行就会中止(接满了一杯,阀门关闭)
咱们来决定什么时候运行剩余的代码next
(何时想喝了再去煮)github
这是Generator
中最重要的特性,咱们只有在真正须要的时候才获取下一个值,而不是一次性获取全部的值ajax
声明Generator
函数有不少种途径,最重要的一点就是,在function
关键字后添加一个*
dom
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
对象很重要:async
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}
复制代码
最经常使用的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
}
// 1
// 2
复制代码
return
不参与迭代
迭代会执行全部的yield
,也就是说,在迭代后的Generator
对象将不会再返回任何有效的值
咱们能够在迭代器对象上直接调用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}
复制代码
在调用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
的语法有点像return
,可是,return
是在函数调用结束后返回结果的
而且在调用return
以后不会执行其余任何的操做
function method (a) {
let b = 5
return a + b
// 下边的两句代码永远不会执行
b = 6
return a * b
}
method(6) // 11
method(6) // 11
复制代码
function * yieldMethod(a) {
let b = 5
yield a + b
// 在执行第二次`next`时,下边两行则会执行
b = 6
return a * b
}
const gen = yieldMethod(6)
gen.next().value // 11
gen.next().value // 36
复制代码
yield*
用来将一个Generator
放到另外一个Generator
函数中执行。
有点像[...]
的功能:
function * gen1 () {
yield 2
yield 3
}
function * gen2 () {
yield 1
yield * gen1()
yield 4
}
let gen = gen2()
gen.next().value // 1
gen.next().value // 2
gen.next().value // 3
gen.next().value // 4
复制代码
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是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 文章中一些的错误,欢迎指出