原文地址https://davidwalsh.name/es6-generators-dive
本文為generator系列文章的第二篇键闺。
如果你依然不熟悉ES6的generator懂昂,建議回去閱讀系列文章的第一篇肾筐。如果您已經(jīng)熟悉Generator基礎(chǔ)用法,接下來可以開始深入去了解一些細節(jié)部分掏颊。
錯誤處理
Generator函數(shù)中有一個非常棒的設(shè)計,就是在generator內(nèi)部代碼是同步的,無論外部是同步還是異步調(diào)用的看锉。
既然內(nèi)部代碼是同步的,那我們就可以在generator內(nèi)部使用try...catch
去處理錯誤塔鳍。
function *foo() {
try {
var x = yield 3;
console.log( "x: " + x ); // may never get here!
}
catch (err) {
console.log( "Error: " + err );
}
}
雖然這個函數(shù)會在yield 3
這個語句停下來伯铣,并且停多久都可以,但是如果一個錯誤被傳入generator函數(shù)轮纫,try...catch
語句還是會捕獲到這個錯誤懂傀。嘗試用普通的異步方式去處理,例如異步函數(shù)蜡感。
但是蹬蚁,怎么將一個錯誤傳入這個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函數(shù),就像這個錯誤是發(fā)生在generator函數(shù)在執(zhí)行yield語句停止的那個位置情连。此時try...catch
就會想預(yù)期那樣的捕獲這個錯誤叽粹。
Note:如果你調(diào)用了
throw(..)
方法,但是沒有用try..catch
去捕獲却舀,這個錯誤會像在普通函數(shù)里一樣被外部捕獲虫几,所以
function *foo() { }
var it = foo();
try {
it.throw( "Oops!" );
}
catch (err) {
console.log( "Error: " + err ); // Error: Oops!
}
內(nèi)部自身錯誤也是如此
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(Delegating Generators)
你也許會想要在generator函數(shù)內(nèi)部調(diào)用另外一個generator函數(shù)。我并不是指用常規(guī)方法實例化一個generator挽拔,而是將你的迭代控制權(quán)委托給另外一個generator函數(shù)辆脸,為了這樣做,我們使用yield語句的另外一種形式yield *
Example
function * foo() {
yield 3;
yield 4;
}
function *bar() {
yield 1;
yield 2;
yield *foo(); // 'yield *' 將迭代控制權(quán)交給foo()
yield 5;
}
for ( var v of bar()) {
console.log(v)
}
// 1 2 3 4 5
讓我們來研究一下這是如何運行的螃诅。在for of
循環(huán)中啡氢,yield 1
和 yield 2
如我們預(yù)期那樣直接輸出值状囱,但是當遇到yield *
語句時,你會發(fā)現(xiàn)我們yield另外一個generator函數(shù)倘是,并且for..of
循環(huán)中直接調(diào)用foo的迭代器亭枷,然后運行完畢后由取回bar的迭代器。
在上面例子中搀崭,我們使用了for of
循環(huán)叨粘,實際上,即使是手動調(diào)用瘤睹,結(jié)果也和上面一樣宣鄙。
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
雖然我們展示往下一級的委托,但是foo也可以繼續(xù)往下委托給別的generator默蚌。
另外一個比較詭異的地方就是yield *
可以從被委托的generator函數(shù)獲取return的值(普通的yield語句的值為通過next方法傳入的)
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()
將交出了迭代控制權(quán)直到交出去的迭代完成了,迭代完成時绸吸,yield *
語句的將返回被委托的generator函數(shù)的返回值(此例中為foo函數(shù),返回值為foo字符串)
這是yield
和yield *
的顯著區(qū)別:yield語句的返回值是通過next()方法傳入的鼻弧,而yield *
語句的返回值是被委托g(shù)enerator函數(shù)的返回值。
你也可以通過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()`(譯者注:當調(diào)用了throw方法攘轩,err被catch后,發(fā)生err的語句之后的代碼都被執(zhí)行码俩,直到下一個yield停下度帮。)
// foo caught: Uh oh!
it.next(); // { value:undefined, done:true } --> No error here!
// bar caught: Oops!
如上面代碼所示,throw('Uh oh!')
語句在foo函數(shù)內(nèi)部拋出了一個錯誤并且被foo函數(shù)內(nèi)部的try...catch所捕獲稿存。類似的笨篷,在foo函數(shù)內(nèi)部的throw 'Oops!'
所拋出的錯誤也被外部的try...catch所捕獲。如果錯誤內(nèi)外都沒有被捕捉瓣履,則會被再外層函數(shù)所捕捉率翅。
總結(jié)
Generator函數(shù)在內(nèi)部代碼有著同步執(zhí)行的特性,這表明你可以使用try...catch來捕捉其中的錯誤袖迎。generator的迭代器的throw方法可以將錯誤拋入generator上一次所停留的位置冕臭,所以也理所當然的可以被try...catch所捕獲。
yield*
語句你將迭代權(quán)交給另一個generator燕锥,yield *
語句的返回值就像個傳遞器辜贵,可以傳遞信息和錯誤。
但是归形,目前還有一個問題沒有回答托慨,generator函數(shù)式如何幫助我們實現(xiàn)異步的,我們目前所了解的都是同步的調(diào)用连霉。關(guān)鍵在于創(chuàng)建一個控制generator暫停和執(zhí)行的一個機制榴芳。我們在下一篇文章會進行講解。