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 */
複製代碼
相關文章
相關標籤/搜索