本篇文章以若干具體的生成器函數爲例,逐步分析其 執行過程,詳細展現生成器函數 內部執行流 和 外部控制流 之間的 消息傳遞機制,包括 普通消息傳遞、異常流傳播 和生成器實例的 主動終結,最後簡單展現 yield*
表達式的執行過程。javascript
Tips: 代碼註釋中的 >>
表示 console.log
打印的內容,->
表示函數調用返回的內容。java
yield
和 next()
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 } */
複製代碼
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 } */
複製代碼
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()
調用處流出。發生這種狀況時,生成器實例迭代結束。對象
每一個生成器實例都從 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;
複製代碼
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 */
複製代碼