JSのGeneratorについて
概要
同期処理を使いたい時にgeneratorを使うという選択肢があるみたいです。
その使い方がよく分からなかったのですが、やっと分かったので記事にしてみます。
どんな動きなのか
取りあえず挙動を見る為にサンプルを書きます。
function* generatorFunc(arg){
console.log(arg); //->"Start"
let inner_variable1 = yield "returned_value1";
console.log(inner_variable1); //->"to use1"
let inner_variable2 = yield "returned_value2";
console.log(inner_variable2); //->"to use2"
}
let gf = generatorFunc("Start");
let value1 = gf.next("to vanish");
let value2 = gf.next("to use1");
let value3 = gf.next("to use2");
console.log(value1); //->{value: "returned_value1", done: false}
console.log(value2); //->{value: "returned_value2", done: false}
console.log(value3); //->{value: undefined, done: true}
これを実行すると、
Start
to use1
to use2
{value: "returned_value1", done: false}
{value: "returned_value2", done: false}
{value: undefined, done: true}
となります。
何が起こっているというの?
このコードを読み取るコツは、generatorFuncの中のyieldを把握することです。
変数gfに関数をセットする際、引数"Start"が渡されますが何も実行しません。
gf.next("to vanish")を行うと最初のyieldまで実行され、"Start"を出力します。
yieldは一回目の実行で一つ、二回目の実行で一つ。
計二つの事をすると覚えるといいです。それは、
- 一回目、yieldの右にある値をオブジェクトに包んで外部の呼び出しの方に返す(returnする)。
- 二回目、= yield された内部変数inner_variableの方にgf.next()実行時の引数を代入する。
なんでしょう?一回通り過ぎたところを、次の実行で戻ってやり直していますね。
僕にとってこれは最初覚えずらい挙動でした。
覚え方
これはどんな役に立てようとしているかをつかめば、しっくりくるものと思います。
let value1 = gf.next("to vanish");
let value2 = gf.next("to use1");
let value3 = gf.next("to use2");
関数とは何か処理をした結果が欲しくて実行するものです。
上のexec_partの関数実行はgf.next("to vanish")の実行で一つ目のyieldの右の値"returned_value1"をオブジェクトにくるんで返し、value1に代入します。その際、"to vanish"は何の働きもしません。
次のgf.next("to use1")の実行で、一つ目のyieldの位置に"to use1"を入れ、結果、
内部変数であるinner_variable1に"to use1"が入ります。
これはつまり、次の様なデータの加工を順々に受け渡して実行する事を意図しているのですね。
let value1 = gf.next("to vanish");
let value2 = gf.next(value1);
let value3 = gf.next(value2);
そうすれば、このgeneratorの便利さが実感されるものと思います。
外部変数value1にreturnされた値を入れ、その結果を反映した関数実行をgf.next(value1)で行って、さらにそれは二個目のyieldでvalue2に返却されて。。。と気持ちよく処理が進んでいくわけです。
一応、それを実例で示す為、以下のコードを載せておきます。
function* generatorFunc(arg){
console.log(arg); //->"Start"
let inner_variable1 = yield arg+"_abc";
console.log(inner_variable1); //->"Start_abc"
let inner_variable2 = yield inner_variable1+"_def";
console.log(inner_variable2); //->"Start_abc_def"
}
let gf = generatorFunc("Start");
let value1 = gf.next("to vanish");
let value2 = gf.next(value1.value);
let value3 = gf.next(value2.value);
console.log(value1); //->{value: "Start_abc", done: false}
console.log(value2); //->{value: "Start_abc_def", done: false}
console.log(value3); //->{value: undefined, done: true}
この結果は以下となります。
Start
Start_abc
Start_abc_def
{value: "Start_abc", done: false}
{value: "Start_abc_def", done: false}
{value: undefined, done: true}
三回目のgf.next()の実行では、処理が完了したことを示すdone: trueとなる事もお忘れなく。valueに関してはもう内部変数に入れる必要が無いので、undefinedと言う訳ですね。
今日はここまで。お疲れさまでした。
Discussion