本文翻譯自:Diving Deeper With ES6 Generatorsjavascript
因爲我的能力有限,翻譯中不免有紕漏和錯誤,望不吝指正issuejava
若是你依然對ES6 generators不是很熟悉,建議你閱讀本系列第一篇文章「第一部分:ES6 Generators基礎指南」,並練習其中的代碼片斷。一旦你以爲對基礎部分掌握透徹了,那咱們就能夠開始深刻理解Generator函數的一些細節部分。異步
ES6 generators設計中最爲強大部分莫過於從語義上理解generator中的代碼都是同步的,儘管外部的迭代控制器是異步執行的。async
也就是說,你可使用簡單的錯誤處理技術來對generators函數進行容錯處理, 也就是你最爲熟悉的try...catch
機制。ide
例如:函數
function *foo() { try { var x = yield 3; console.log( "x: " + x ); // may never get here! } catch (err) { console.log( "Error: " + err ); } }
儘管上面例子中的foo
generator函數會在yield 3
表達式後暫停執行,而且可能暫停任意長的時間,若是向generator函數內部傳入一個錯誤,generator函數內部的try...catch
模塊將會捕獲傳入的錯誤!就像經過回調函數等常見的異步處理機制同樣來處理錯誤。:)
可是,錯誤到底是怎樣傳遞到generator函數內部的呢?
var it = foo(); var res = it.next(); // { value:3, done:false } // instead of resuming normally with another `next(..)` call, // let's throw a wrench (an error) into the gears: it.throw( "Oops!" ); // Error: Oops!
如上代碼,你會看到iterator的另一個方法- -throw(..)
- -,該方法向generator函數內部傳入一個錯誤,該錯誤就如同在generator函數內部暫停執行的yield
語句處拋出的錯誤同樣,正如你所願,try...catch
模塊捕獲了經過throw
方法拋出的錯誤。
注意:若是你經過throw(..)
方法向generator函數內部拋出一個錯誤,同時在函數內部又沒有try...catch
模塊來捕獲錯誤,該錯誤(如同正常的錯誤冒泡機制)將從generator函數冒泡到函數外部(若是始終都沒對該錯誤進行處理,該錯誤將冒泡到最外層成爲未捕獲錯誤)。代碼以下:
function *foo() { } var it = foo(); try { it.throw( "Oops!" ); } catch (err) { console.log( "Error: " + err ); // Error: Oops! }
顯而易見,反向的錯誤處理依然可以正常工做(譯者注:generator函數內部拋出錯誤,在generator外部捕獲):
function *foo() { var x = yield 3; var y = x.toUpperCase(); // could be a TypeError error! yield y; } var it = foo(); it.next(); // { value:3, done:false } try { it.next( 42 ); // `42` won't have `toUpperCase()` } catch (err) { console.log( err ); // TypeError (from `toUpperCase()` call) }
在使用generator函數的過程當中,另一件你可能想要作的事就是在generator函數內部調用另一個generator函數。這兒我並非指在普通函數內部執行generator函數,其實是把迭代控制權委託給另一個generator函數。爲了完成這件工做,咱們使用了yield
關鍵字的變種:yield *
(「yield star」)。
例如:
function *foo() { yield 3; yield 4; } function *bar() { yield 1; yield 2; yield *foo(); // `yield *` delegates iteration control to `foo()` yield 5; } for (var v of bar()) { console.log( v ); } // 1 2 3 4 5
在第一篇文章中已經說起(在第一篇文章中,我使用function *foo() { }
的語法格式,而不是function* foo() { }
),在這裏,咱們依然使用yield *foo()
,而不是yield* foo()
,儘管不少文章/文檔喜歡採用後面一種語法格式。我認爲前面一種語法格式更加準確/清晰得表達此語法含義。
讓咱們來分解上面代碼是如何工做的。yield 1
和yield 2
表達式直接將值經過for..of
循環(隱式)調用next()
傳遞到外部,正如咱們已經理解並期待的那樣。
在代碼執行過程當中,咱們遇到了yield *
表達式,你將看到咱們經過執行foo()
將控制權交給了另一個generator函數。所以咱們基本上就是出產/委託給了另一個generator函數的迭代器- -也許這就是最準確的理解代理generator函數如何工做的。
一旦yield *
表達式(臨時的)在*bar()
函數中將控制權委託給*foo()
函數,那麼如今for..of
循環中的next()
方法的執行將徹底控制foo()
,所以yield 3
和yield 4
表達式將他們的值經過for..of
循環返回到外部。
當*foo()
運行結束,控制權從新交回最初的generator函數,最後在外層bar
函數中執行yield 5
。
簡單起見,在上面的實例中,咱們僅經過yield
表達式將值傳遞到generator函數外部,固然,若是咱們不用for..of
循環,而是手動的執行迭代器的next()
方法來向函數內部傳遞值,這些值也會按你所期待的方式傳遞給經過yield *
代理的generator函數中:
function *foo() { var z = yield 3; var w = yield 4; console.log( "z: " + z + ", w: " + w ); } function *bar() { var x = yield 1; var y = yield 2; yield *foo(); // `yield*` delegates iteration control to `foo()` var v = yield 5; console.log( "x: " + x + ", y: " + y + ", v: " + v ); } var it = bar(); it.next(); // { value:1, done:false } it.next( "X" ); // { value:2, done:false } it.next( "Y" ); // { value:3, done:false } it.next( "Z" ); // { value:4, done:false } it.next( "W" ); // { value:5, done:false } // z: Z, w: W it.next( "V" ); // { value:undefined, done:true } // x: X, y: Y, v: V
儘管上面的代碼中咱們只展現了嵌套一層的代理generator函數,可是沒有理由*foo()
不能夠經過yield *
表達式繼續代理其餘的generator迭代器,甚至繼續嵌套代理其餘generator函數,等等。
yield *
表達式能夠實現另一個竅門,就是yield *
表達式將會返回被代理generator函數的函數返回值。
function *foo() { yield 2; yield 3; return "foo"; // return value back to `yield*` expression } function *bar() { yield 1; var v = yield *foo(); console.log( "v: " + v ); yield 4; } var it = bar(); it.next(); // { value:1, done:false } it.next(); // { value:2, done:false } it.next(); // { value:3, done:false } it.next(); // "v: foo" { value:4, done:false } it.next(); // { value:undefined, done:true }
正如你所見,yield *foo()
正在代理迭代器的控制權(調用next()
方法)至到其運行完成,當前執行完成,foo()
函數的函數return
值(本例中是"foo"
字符串)將會做爲yield *
表達式的值,在上例中將該值賦值給變量v
。
這是一個yield
和yield*
表達式有趣的區別:在yield
表達式中,表達式的返回值是經過隨後的next()
方法調用傳遞進來的,可是在yield *
表達式中,它將獲取到被代理generator函數的return
值(由於next()
方法顯式的將值傳遞到被代理的generator函數中)。
你依然能夠雙向的對yield *
代理進行錯誤處理(如上所述):
function *foo() { try { yield 2; } catch (err) { console.log( "foo caught: " + err ); } yield; // pause // now, throw another error throw "Oops!"; } function *bar() { yield 1; try { yield *foo(); } catch (err) { console.log( "bar caught: " + err ); } } var it = bar(); it.next(); // { value:1, done:false } it.next(); // { value:2, done:false } it.throw( "Uh oh!" ); // will be caught inside `foo()` // foo caught: Uh oh! it.next(); // { value:undefined, done:true } --> No error here! // bar caught: Oops!
如你所見,throw("Uh oh!")
經過yield*
代理將錯誤拋出,而後*foo()
函數內部的try..catch
模塊捕獲到錯誤。一樣地,在*foo()
函數內部經過throw "Oops!"
拋出錯誤冒泡到*bar()
函數中被另一個try..catch
模塊捕獲,若是咱們沒有捕獲到其中的某一條錯誤,該錯誤將會按你所期待的方式繼續向上冒泡。
Generators函數擁有同步執行的語義,這也意味着你能夠經過try..catch
錯誤處理機制來橫跨yield
語句進行錯誤處理。同時,generator迭代器有一個throw()
方法來向generator函數中暫停處拋出一個錯誤,該錯誤依然能夠經過generator函數內部的try..catch
模塊進行捕獲處理。
yield *
關鍵字容許你將迭代控制權從當前generator函數委託給其餘generator函數。結果就是,yield *
將扮演一個雙向的信息和錯誤傳遞角色。
可是到目前爲止,一個基礎的問題依然沒有解決:generator函數怎麼幫助咱們處理異步模式?在以上兩篇文章中咱們一直討論generator函數的同步迭代模式。
構想generator函數異步機制的關鍵點在於,經過generator函數的暫停執行來開始一個異步任務,而後經過generator函數的從新啓動(經過迭代器的next()
方法的執行)來結束上面的異步任務。咱們能夠在接下來的文章中發現generator函數形式各樣的異步控制機制。近期期待!