本篇文章以若干具体的生成器函数为例,逐步分析其 执行过程,详细展示生成器函数 内部执行流 和 外部控制流 之间的 消息传递机制,包括 普通消息传递、异常流传播 和生成器实例的 主动终结,最后简单展示 yield* 表达式的执行过程。
Tips: 代码注释中的 >> 表示 console.log 打印的内容,-> 表示函数调用返回的内容。
1. yield 和 next()
yield ..和next(..)这一对组合起来,在生成器的执行过程中构成一个双向消息传递系统——《你不知道的JavaScript》(中卷)
function * genFunc() {
console.log('1st next()') // inner_1
console.log(yield 'yieldValue_1') // inner_2
console.log('2nd next()') // inner_3
console.log(yield 'yieldValue_2') // inner_4
console.log('3rd next()') // inner_5
return 'returnValue' // inner_6
}
// 调用 genFunc() 获得一个生成器实例
const gen = genFunc()
// gen.next() 可接收一个参数,无参调用等同于传入一个 undefined 参数
gen.next()
/**
* 第一次调用 next(),启动 generator,开始执行 genFunc() 函数体代码
* inner_1: >> 1st next()
* inner_2: 碰到 yield 语句
* 1. 计算 yield 后面的表达式,保存计算值 value
* 2. 暂停执行 genFunc(),等待下一次 next() 调用
* 3. 此次 next() 调用返回 { value, done: false }
*
* -> { value: 'yieldValue_1', done: false }
*/
gen.next('nextArg_1')
/**
* 恢复执行 genFunc()
* inner_2: yield 接收 next() 传进来的参数 'nextArg_1',
* 作为 yield 语句的计算值
* >> nextArg_1
* inner_3: >> 2nd next()
* inner_4: 碰到 yield 语句
* 1. 计算 yield 后面的表达式,保存计算值 value
* 2. 暂停执行 genFunc(),等待下一次 next() 调用
* 3. 此次 next() 调用返回 { value, done: false }
*
* -> { value: 'yieldValue_2', done: false }
*/
gen.next('nextArg_2')
/**
* 恢复执行 genFunc()
* inner_4: yield 接收 next() 传进来的参数 'nextArg_2',
* 作为 yield 语句的计算值
* >> nextArg_1
* inner_5: >> 3rd next()
* inner_6: 碰到 return 语句
* 1. 计算 return 语句后面的表达式,保存计算值 value
* 2. genFunc() 执行结束
* 3. 此次 next() 调用返回 { value, done: true }
*
* 若 genFunc() 不含 return 语句,则当 genFunc 执行结束时,
* 相应的 next() 调用返回 { value: undefined, done: false }
*
* -> { value: 'returnValue', done: true }
*/
gen.next('anyArgs')
/**
* 此后每次 next() 调用都返回 { value: undefined, done: true }
*/
2. 生成器的异常流
2.1 生成器函数内部抛出的异常从相应的 next() 调用处流出
若生成器函数内部抛出的异常未在函数内部被捕获,则该异常从相应的 next() 调用处流出。
function *genFunc() {
yield 'yieldValue' // inner_1
throw 'innerExceptionValue' // inner_2
return 'returnValue' // inner_3
}
const gen = genFunc()
gen.next()
/**
* -> { value: 'yieldValue', done: false }
*/
try {
gen.next() // outer_1
} catch ('ouer catch:', value) { // outer_2
console.log(value) // outer_3
}
/**
* outer_1: gen.next() 恢复 genFunc() 函数的执行,
* inner_2: 抛出异常,该异常没有在 genFunc() 内部被捕获
* 1. genFunc() 异常结束
* 2. 生成器实例 gen 迭代结束,此后调用 gen.next()
* 总是返回 { value: undefined, done: true }
* outer_1: genFunc() 内部的抛出的异常从 gen.next() 流出
* outer_2: 距离最近的 catch 语句捕获了异常
* outer_3: >> outer catch: innerExceptionValue
*/
gen.next()
/**
* -> { value: undefined, done: true }
*/
2.2 Generator.prototype.throw() 抛出的异常从相应的 yield 处流出
每个生成器实例都从 Generator.prototype 继承了 throw() 方法。
gen.throw() 抛出的异常首先流入生成器函数内部,从相应的 yield 处流出。该异常可在生成器函数内部捕获。
function *genFunc() {
try {
yield 'yieldValue_1' // inner_1
} catch (value) { // inner_2
console.log('inner catch:', value) // inner_3
}
yield 'yieldValue_2' // inner_4
return 'returnValue' // inner_5
}
const gen = genFunc()
gen.next()
/**
* -> { value: 'yieldValue_1', done: false }
*/
gen.throw('outerExceptionValue') // outer_1
/**
* outer_1:
* 1. gen.throw() 抛出异常
* 2. genFunc() 恢复执行
* 3. 异常流入 genFunc() 内部
* inner_1: 异常从 yield 语句流出
* inner_2: 距离最近的 catch 语句捕获该异常
* inner_3: >> inner catch: outerExceptionValue
* inner_4: 碰到 yield 语句
*
* -> { value: 'yieldValue_2', done: false }
*/
gen.next()
/**
* -> { value: 'returnValue', done: true }
*/
若 gen.throw() 抛出的异常在生成器函数内部没有被捕获,则该异常从 gen.throw() 处流出。
若 gen.throw() 抛出的异常在生成器函数内部被捕获,此次 gen.throw() 调用触发的函数执行过程中,若有其他未被捕获的异常,也会从 gen.throw() 处流出。
所有没有在生成器函数内部捕获的异常都会从相应的 gen.next() 或 gen.throw() 调用处流出。发生这种情况时,生成器实例迭代结束。
3. 主动终结生成器实例
每个生成器实例都从 Generator.prototype 继承了 return() 方法。
调用 gen.return(val) 可主动终结生成器实例,返回 { value: val, done: true }。若调用时不提供参数,返回值为 { value: undefined, done: true }。
next()、throw()、return()的共同点
next()、throw()、return()这三个方法本质上是同一件事,可以放在一起理解。它们的作用都是让 Generator 函数恢复执行,并且使用不同的语句替换yield表达式。
next()是将yield表达式替换成一个值。
const g = function* (x, y) {
let result = yield x + y;
return result;
};
const gen = g(1, 2);
gen.next(); // Object {value: 3, done: false}
gen.next(1); // Object {value: 1, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = 1;
上面代码中,第二个
next(1)方法就相当于将yield表达式替换成一个值1。如果next方法没有参数,就相当于替换成undefined。
throw()是将yield表达式替换成一个throw语句。
gen.throw(new Error('出错了')); // Uncaught Error: 出错了
// 相当于将 let result = yield x + y
// 替换成 let result = throw(new Error('出错了'));
return()是将yield表达式替换成一个return语句。
gen.return(2); // Object {value: 2, done: true}
// 相当于将 let result = yield x + y
// 替换成 let result = return 2;
4. yield* 表达式(yield 委托)
语法:
yield* [[expression]]
expression时返回一个可迭代对象的表达式。
yield*表达式迭代操作数,并产生它返回的每个值。
yield*表达式本身的值是当迭代器关闭时返回的值(即done为true时)—— MDN yield*
function* foo() {
yield 'foo1'
yield 'foo2'
}
function* bar() {
yield 'bar1'
yield 'bar2'
return 'bar'
}
function* baz() {
yield 'baz1'
console.log('yield* foo() return:', yield* foo())
console.log('yield* foo() return:', yield* bar())
yield 'baz2'
}
const gen = baz()
for (let v of gen) {
console.log(v)
}
/* >>
baz1
foo1
foo2
yield* foo() return: undefined
bar1
bar2
yield* foo() return: bar
baz2
*/