JavaScript Generator 函数的执行过程和消息传递机制

本篇文章以若干具体的生成器函数为例,逐步分析其 执行过程,详细展现生成器函数 内部执行流外部控制流 之间的 消息传递机制,包括 普通消息传递异常流传播 和生成器实例的 主动终结,最后简单展现 yield* 表达式的执行过程。javascript

Tips: 代码注释中的 >> 表示 console.log 打印的内容,-> 表示函数调用返回的内容。java

1. yieldnext()

yield ..next(..) 这一对组合起来,在生成器的执行过程当中构成一个双向消息传递系统es6

——《你不知道的JavaScript》(中卷)express

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() 方法。ui

gen.throw() 抛出的异常首先流入生成器函数内部,从相应的 yield 处流出。该异常可在生成器函数内部捕获。spa

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() 处流出。prototype

gen.throw() 抛出的异常在生成器函数内部被捕获,这次 gen.throw() 调用触发的函数执行过程当中,如有其余未被捕获的异常,也会从 gen.throw() 处流出。code

全部没有在生成器函数内部捕获的异常都会从相应的 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;
复制代码

—— 《ESCMAScript 6 入门》阮一峰

4. yield* 表达式(yield 委托)

语法:yield* [[expression]]

expression 时返回一个可迭代对象的表达式。

yield* 表达式迭代操做数,并产生它返回的每一个值。

yield* 表达式自己的值是当迭代器关闭时返回的值(即 donetrue 时)

—— 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 */
复制代码
相关文章
相关标签/搜索