ES 6 Generatorsを深く研究する
13015 ワード
ES 6 Generatorsシリーズ:
エラー処理
ES 6 generators設計で最も強力な部分の一つは、generator関数の外部制御が非同期であってもgenerator関数内部のコードが同期されていることである.
すなわち、tryを使用するなど、generator関数でエラーを簡単に処理するために、よく知られているエラー処理メカニズムを使用することができます.catchメカニズム.
例を見てみましょう
function *foo() {
try {
var x = yield 3;
console.log( "x: " + x ); // !
}
catch (err) {
console.log( "Error: " + err );
}
}
関数はyield 3式の位置で任意の長い時間停止するが、エラーがgenerator関数に返されるとtry.catchは依然としてこのエラーをキャプチャします!非同期コールバックで上記のコードを呼び出してみてください.
では、generator関数にエラーを正確に返すにはどうすればいいのでしょうか.
var it = foo();
var res = it.next(); // { value:3, done:false }
// next(..) , :
it.throw( "Oops!" ); // Error: Oops!
ここではもう一つの方法throw(.)を用いて、generator関数が一時停止した位置でエラーが投げ出されtry.catch文はこのエラーをキャプチャします!
注意:throwを通過したら(..)メソッドはgenerator関数にエラーを投げ出すが、このgenerator関数にはtryはない.catch文がエラーをキャプチャすると、このエラーが返されます(このエラーが他のコードにキャプチャされていない場合は、未処理の例外として投げ出されます).したがって、
function *foo() { }
var it = foo();
try {
it.throw( "Oops!" );
}
catch (err) {
console.log( "Error: " + err ); // Error: Oops!
}
明らかに、逆方向のエラー処理も可能です.次のコードを見てください.
function *foo() {
var x = yield 3;
var y = x.toUpperCase(); // !
yield y;
}
var it = foo();
it.next(); // { value:3, done:false }
try {
it.next( 42 ); // 42 toUpperCase()
}
catch (err) {
console.log( err ); // toUpperCase() TypeError
}
Generators依頼
通常の方法でgenerator関数をインスタンス化するのではなく、現在のgenerator関数の反復制御を別のgenerator関数に委任するgenerator関数内で別のgenerator関数を呼び出すことができます.キーワードyield*で実現します.次のコードを見てください.
function *foo() {
yield 3;
yield 4;
}
function *bar() {
yield 1;
yield 2;
yield *foo(); // yield * generator foo()
yield 5;
}
for (var v of bar()) {
console.log( v );
}
// 1 2 3 4 5
ここではyield*foo()ではなくyield*foo()という書き方を推奨していますが、前の記事でもこの点について言及しました(function*foo(){}function*foo(){}).実際には、他の多くの文章やドキュメントにも前者が採用されています.このような書き方は、コードをより明確に見せることができます.
上のコードの動作原理を見てみましょう.for..ofループループでは,暗黙的にnext()メソッドを呼び出して式yield 1とyield 2の値を返すことを前の論文で解析した.キーワードyield*の位置で、プログラムをインスタンス化し、反復制御を別のgenerator関数foo()に委任します.反復制御をyield*で*bar()から*foo()に委任すると(一時的な)、for..ofループはnext()メソッドでfoo()を巡回するので、式yield 3とyield 4は対応する値をforに返す.ofサイクル.*foo()へのループが終了すると、委任制御は以前のgenerator関数に戻り、式yield 5は対応する値を返す.
上のコードは簡単ですが、yield式で値を出力します.もちろん、あなたはforを通過しなくてもいいです.ofループで手動でnextを通過(.)メソッドは、yield*キーワードを介して対応するyield式に伝達される対応する値を転送して巡回します.次の例を見てください.
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
ここでは1次依頼のみを示したが,理論的には任意の多段依頼が可能である,すなわち,前例のgenerator関数*foo()にはyield*式もあり,制御をさらに別のgenerator関数に委任し,1次1次伝達することができる.
もう1つはyield*式が依頼されたgenerator関数のreturn戻り値を受信できることです.
function *foo() {
yield 2;
yield 3;
return "foo"; // "foo" yield *
}
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()式を通じて、プログラムはgenerator関数*foo()に制御を委任し、関数foo()の実行が完了した後、return文を通じて値(文字列「foo」)をyield*式に返し、bar()関数でこの値は最終的に変数vに割り当てられる.
Yieldとyield*の間には興味深い違いがあります.yield式では、受信した値は次のnext(.)です.メソッドが入力するパラメータですが、yield*式では、依頼されたgenerator関数のreturn文から返される値が受信されます(next(..)メソッドが値を入力するプロセスは透過的です).
yield*の依頼で双方向エラー処理を行うこともできます.
function *foo() {
try {
yield 2;
}
catch (err) {
console.log( "foo caught: " + err );
}
yield; //
//
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!" ); // foo() try..catch
// foo caught: Uh oh!
it.next(); // { value:undefined, done:true } --> !
// bar caught: Oops!
上のコードではthrow("Uh oh!")メソッドは、yield*によって委任されたgenerator関数*foo()のtry.catchがキャプチャしました.同様に、*foo()のthrow「Oops!」文はエラーを*bar()に戻し、*bar()のtryに返されます.catchキャプチャ.エラーがキャプチャされていない場合は、上へ放出され続けます.
まとめ
コードの意味の面から見ると、generator関数は同期的に実行され、yield文でtryを使用できることを意味します.catchでエラーを処理します.また、generator遍歴器にはthrow(.)方法は、一時停止した場所でエラーを投げ出すことができ、このエラーはgenerator関数内部のtry.catchキャプチャ.
キーワードyield*を使用すると、現在のgenerator関数の内部で別のgenerator関数を委任して移動できます.パラメータをyield*を介して委任されたgenerator関数体に渡すことができます.もちろん、エラー情報もyield*を介して返されます.
これまで,非同期モードでgenerator関数をどのように使用するかという最も基本的な質問には答えられなかった.前に見たgenerator関数の遍歴はすべて同期して実行されます.
重要なのは、generator関数が一時停止中に非同期タスクを開始し、非同期タスクの終了時にgenerator関数の実行を再開するメカニズムを構築することです(next()メソッドを呼び出すことによって).次の論文ではgenerator関数においてこのような非同期制御を作成するための様々な方法を検討する.注目してください!