Open
Description
概念
一种异步编程的解决方案,与传统函数的语法、行为完全不同
语法
function
关键字与函数名之间加个 *
号。
function* gen () {}
内部使用 yield
关键字,执行后返回一个迭代器(Iterator
),没错,就是用 next
方法去遍历的迭代器:
function* gen () {
yield 1
yield 1 + 1
return 3
}
// Iterator
const it = gen()
it.next() // { value: 1, done: false }
it.next() // { value: 2, done: false }
it.next() // { value: 3, done: true }
可以使函数“暂停”在某个阶段,等到执行了 next
方法后再继续执行。
Generator 函数返回的迭代器在没有执行 next
是不会执行的:
function* gen () {
console.log('executed')
}
const it = gen()
it.next()
// executed
另外注意, yield
关键字在其他表达式中,需要用括号包起来,不然会报错:
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
for...of 循环
既然是 Iterator ,就可以用 for...of
循环来遍历它:
function* gen () {
yield 1
yield 2
yield 3
return 4
}
for (let value of gen()) {
console.log(value)
}
// 1 2 3
// 注意, `for...of` 循环不会输出结果为 `done: true` 的值
next 函数的参数
在执行 next
时,如果给它传一个参数,这个参数会作为上一个 yield
表达式的返回值。
如果不传,其实就相当于传了 undefined
function* gen () {
const i = yield 1
console.log(i)
yield i + 1
}
const it = gen()
const y1 = it.next() // { value: 1, done: false }
it.next(y1.value + 1)
// 函数内部输出 2
// next 返回值: { value: 3, done: false }
it.next() // { value: undefined, done: true }
Generator.prototype.throw()
可以在 Generator 函数内部抛出一个错误,如果 Generator 内部没有处理,则会抛出到外部:
function* gen () {
try {
yield
} catch (e) {
console.log('Generator 函数内部捕获', e)
}
}
const it = gen()
it.next() // 先执行到第一个 yield 处
it.throw('手动抛出一个错误')
// Generator 函数内部捕获 手动抛出一个错误
Generator.prototype.return()
相当于提前把这个迭代器结束遍历,即返回的 done
变为 true
function* gen () {
yield 1
yield 2
yield 3
return 4
}
const it = gen()
it.next() // { value: 1, done: false }
it.return() // { value: undefined, done: true }
it.next() // { value: undefined, done: true }
return
的第一个参数可以指定 value 的值:
function* gen () {
yield 1
yield 2
yield 3
return 4
}
const it = gen()
it.next() // { value: 1, done: false }
it.return('foo') // { value: 'foo', done: true }
it.next() // { value: undefined, done: true }
next, throw, return
三个函数其实可以理解为,把执行函数时起点的 yield
替换成了不同的语句:
function* gen () {
const r = yield 1
}
const it = gen()
it.next() // 执行到第一个 yield
it.next()
// 相当于把 `const r = yield 1` 替换成 `const r = undefined`
// it.throw(new Error('error'))
// 相当于把 `const r = yield 1` 替换成 `const r = throw new Error('error')`
// it.return(6)
// 相当于把 `const r = yield 1` 替换成 `const r = return 6`
yield* 表达式
如果在 Generator 函数内部调用 Generator 函数,可以用 yield*
表达式,这样外层在遍历 Generator 时,遇到内部的 Generator 函数,会转而进入内部函数遍历,不会跳过:
function* bar () {
yield 1
yield 2
}
function* foo () {
yield 3
// 这边如果没有手动去遍历 `bar()` 返回的迭代器, `foo` 的下一个 `next` 函数就会执行到 `yield 4` 了
bar()
yield 4
}
const it = foo()
for (let value of it) {
console.log(value)
}
// 3 4
手动遍历:
function* bar () {
yield 1
yield 2
}
function* foo () {
yield 3
for (let value of bar()) {
yield value
}
yield 4
}
const it = foo()
for (let value of it) {
console.log(value)
}
// 3 1 2 4
使用 yield*
表达式:
function* bar () {
yield 1
yield 2
}
function* foo () {
yield 3
yield* bar()
yield 4
}
const it = foo()
for (let value of it) {
console.log(value)
}
// 3 1 2 4
作为对象属性
Generator 函数作为对象属性的写法:
const obj = {
* generatorProperty () {
yield 1
}
}
this
由于 Generator 函数返回的是一个 Iterator ,而不是 this 对象,因此在 Generator 函数内绑定在 this
上的属性都是无效的:
function* gen () {
this.a = 1
}
const it = gen()
console.log(it instanceof gen) // true , 这是 ES6 规定的,返回的迭代器是 Generator 函数的实例
console.log(it.a) // undefined
此外, Generator 函数也不能作为构造函数而使用 new
命令:
function* gen () {
this.a = 1
}
const it = new gen() // TypeError: gen is not a constructor
Generator 与协程、上下文,应用
这部分见峰哥的文章吧。