Generator
# Generator
Generators 是可以用来控制迭代器的函数。它们可以暂停,然后在任何时候恢复。
常规循环:
for (let i = 0; i < 5; i += 1) {
console.log(i)
}
// 立即依次打印出: 0, 1, 2, 3, 4
1
2
3
4
2
3
4
利用 Generator:
function* generatorForLoop() {
for (let i = 0; i < 5; i += 1) {
yield console.log(i)
}
}
const genForLoop = generatorForLoop()
console.log(genForLoop.next()) // 0
console.log(genForLoop.next()) // 1
console.log(genForLoop.next()) // 2
console.log(genForLoop.next()) // 3
console.log(genForLoop.next()) // 4
console.log(genForLoop.next()) // undefined
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
常规的循环只能一次遍历完所有值,Generator 可以通过调用 next()
方法拿到依次遍历的值,让遍历的执行变得「可控」。
# 基本语法
# 定义
function* gen() {
yield 1
yield 2
yield 3
return 4
}
let g = gen()
g.next() // {value: 1, done: false}
g.next() // {value: 2, done: false}
g.next() // {value: 3, done: false}
g.next() // {value: 4, done: true}
g.next() // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
这个是 Generator 的定义方法,有几个点值得注意:
- 比普通函数多一个
*
- 函数内部用
yield
来控制程序的执行的「暂停」 - 函数的返回值通过调用
next()
来「恢复」程序执行
注意
Generator 函数的定义不能使用箭头函数,否则会触发 SyntaxError 错误
let generator = * () => {} // Uncaught SyntaxError: Unexpected token '*'
let generator = () * => {} // Uncaught SyntaxError: Unexpected token ')'
let generator = ( * ) => {} // Uncaught SyntaxError: Unexpected token '*'
1
2
3
2
3
# yield 表达式
yield 关键字用来暂停和恢复一个生成器函数
关于 yield
表达式,要熟记几个知识点:
yield
表达式的返回值是undefined
,但是遍历器对象的next()
方法可以修改这个默认值。
function* gen() {
let val
val = yield 1
console.log( `1:${val}` ) // 1:undefined
val = yield 2
console.log( `2:${val}` ) // 2:undefined
val = yield 3
console.log( `3:${val}` ) // 3:undefined
}
let g = gen()
console.log(g.next()) // {value: 1, done: false}
console.log(g.next()) // {value: 2, done: false}
console.log(g.next()) // {value: 3, done: false}
console.log(g.next()) // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
yield*
表达式用于将值的生成过程委托给另一个生成器或可遍历对象。
// 如果在 Generator 函数内部, 调用另一个 Generator 函数
// 需要在前者的函数体内部, 自己手动完成遍历
function* g1() {
yield 2
yield 3
yield 4
}
function* g2() {
yield 1
yield g1()
yield 5
}
const iterator = g2()
console.log(iterator.next()) // {value: 1, done: false}
console.log(iterator.next()) // {value: g1, done: false}
console.log(iterator.next()) // {value: 5, done: false}
console.log(iterator.next()) // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 使用 yield* 表达式
// g1() yield 出去的每个值都会在 g2() 的 next() 方法中返回, 就像那些 yield 语句是写在 g2() 里一样
function* g1() {
yield 2
yield 3
yield 4
}
function* g2() {
yield 1
yield* g1()
yield 5
}
const iterator = g2()
console.log(iterator.next()) // {value: 1, done: false}
console.log(iterator.next()) // {value: 2, done: false}
console.log(iterator.next()) // {value: 3, done: false}
console.log(iterator.next()) // {value: 4, done: false}
console.log(iterator.next()) // {value: 5, done: false}
console.log(iterator.next()) // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- Generator 对象的
next()
方法,遇到yield
就暂停,并返回一个对象,这个对象包括两个属性:value
和done
。
# 方法
Generator 对象有几个方法,next
、return
、throw
。
- next([value])
next()
方法可以获取每一次遍历的结果,返回的对象包含两个属性:value
(当前程序的运行结果) 和done
(遍历是否结束)。next()
方法可以接受参数,这个参数用于在 Generator 外部给内部传递数据,而这个参数就是作为yield
的返回值。
function* gen() {
let val = 100
while (true) {
console.log( `before ${val}` )
val = yield val
console.log( `return ${val}` )
}
}
let g = gen()
console.log(g.next(20).value)
// before 100
// 100
console.log(g.next(30).value)
// return 30
// before 30
// 30
console.log(g.next(40).value)
// return 40
// before 40
// 40
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- return()
return()
方法可以让 Generator 遍历终止,有点类似 for 循环的 break。
function* gen() {
yield 1
yield 2
yield 3
}
let g = gen()
console.log(g.next()) // {value: 1, done: false}
console.log(g.return()) // {value: undefined, done: true}
console.log(g.next()) // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
return()
也可以传入参数,作为返回的 value
值。
function* gen() {
yield 1
yield 2
yield 3
}
let g = gen()
console.log(g.next()) // {value: 1, done: false}
console.log(g.return(100)) // {value: 100, done: true}
console.log(g.next()) // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
- throw()
可以通过 throw 方法在 Generator 外部控制内部执行的「终断」。
function* gen() {
while (true) {
try {
yield 42
} catch (e) {
console.log(e.message)
}
}
}
let g = gen()
console.log(g.next()) // { value: 42, done: false }
console.log(g.next()) // { value: 42, done: false }
console.log(g.next()) // { value: 42, done: false }
// 中断操作
g.throw(new Error('break'))
console.log(g.next()) // {value: undefined, done: true}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
提示
如果想退出遍历 catch
之后可以配合 return false
使用,能起到 「break」 的作用
# 应用场景
# 场景 1
使用 Generator,按顺序读取 a.json、b.json、c.json。
function request(url) {
ajax(url, res => {
getData.next(res)
})
}
function* gen() {
let res1 = yield request('static/a.json')
console.log(res1)
let res2 = yield request('static/b.json')
console.log(res2)
let res3 = yield request('static/c.json')
console.log(res3)
}
let getData = gen()
getData.next()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 场景 2
酒桌小游戏敲7,无限循环转圈去数数,遇到 7 和 7 的倍数就敲桌子。只要调用 n.next
我们就可以知道下一个需要敲桌子的数字是什么了。
function* count(x = 1) {
while (true) {
if (x % 7 === 0) {
yield x
}
x++
}
}
// ES5 中就是个死循环 因为 ES5 的循环需要有个终止值,当前这个需求没有终止,一直在数数
let n = count()
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
console.log(n.next().value)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 参考资料
(完)