通俗的讲 Generators
是可以用来控制迭代器的函数。
// 常规循环
for (let i = 0; i < 5; i += 1) {
console.log(i)
}
// 利用 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
常规的循环只能一次遍历完所有值,Generator
可以通过调用 next
方法拿到依次遍历的值,让遍历的执行变得“可控”。
1. 基本语法
function* gen() {
yield 1
yield 2
yield 3
}
let g = gen()
// "Generator { }"
定义方法注意点:
- 比普通函数多一个 *
- 函数内部用
yield
来控制程序的执行的“暂停” - 函数的返回值通过调用
next
来“恢复”程序执行
Generator 函数的定义不能使用箭头函数,否则会触发
SyntaxError
错误
yield 表达式
yield
关键字用来暂停和恢复一个生成器函数。
yield
表达式的返回值是undefined
,但是遍历器对象的next
方法可以修改这个默认值。yeild *
是委托给另一个遍历器对象或者可遍历对象Generator
对象的next
方法,遇到yield
就暂停,并返回一个对象,这个对象包括两个属性:value
和done
。
方法
Generator 对象有几个方法,next
、return
、throw
。
function* gen() {
var val = 100
while (true) {
console.log( `before ${val}` )
val = yield val
console.log( `return ${val}` )
}
}
var 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.g.next(20) 这句代码会执行 gen 内部的代码,遇到第一个 yield 暂停。所以 console.log( before ${val} ) 执行输出了 before 100 ,此时的 val 是 100,所以执行到 yield val 返回了 100,注意 yield val 并没有赋值给 val。
2.g.next(30) 这句代码会继续执行 gen 内部的代码,也就是 val = yield val 这句,因为 next 传入了 30,所以 yield val 这个返回值就是 30,因此 val 被赋值 30,执行到 console.log( return ${val} ) 输出了 30,此时没有遇到 yield 代码继续执行,也就是 while 的判断,继续执行 console.log( before ${val} ) 输出了 before 30 ,再执行遇到了 yield val 程序暂停。
3.g.next(40) 重复步骤 2。
return 方法可以让 Generator 遍历终止,有点类似 for 循环的 break:
function* gen() {
yield 1
yield 2
yield 3
}
var 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}
从 done 可以看出代码执行已经结束。当然 return 也可以传入参数,作为返回的 value 值。
function* gen() {
yield 1
yield 2
yield 3
}
var 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}
可以通过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}
如果想退出遍历 catch 之后可以配合 return false 使用,能起到 “break” 的作用
2. 应用场景
场景1:顺序请求读取文件
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()
场景2:无限循环但不会导致崩溃
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)