😉

JSのGeneratorについて

2020/12/08に公開

概要

同期処理を使いたい時にgeneratorを使うという選択肢があるみたいです。
その使い方がよく分からなかったのですが、やっと分かったので記事にしてみます。

どんな動きなのか

取りあえず挙動を見る為にサンプルを書きます。

generator1.js
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}

これを実行すると、

result
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は一回目の実行で一つ、二回目の実行で一つ。
計二つの事をすると覚えるといいです。それは、

  1. 一回目、yieldの右にある値をオブジェクトに包んで外部の呼び出しの方に返す(returnする)。
  2. 二回目、= yield された内部変数inner_variableの方にgf.next()実行時の引数を代入する。

なんでしょう?一回通り過ぎたところを、次の実行で戻ってやり直していますね。
僕にとってこれは最初覚えずらい挙動でした。

覚え方

これはどんな役に立てようとしているかをつかめば、しっくりくるものと思います。

exec_part
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"が入ります。

これはつまり、次の様なデータの加工を順々に受け渡して実行する事を意図しているのですね。

exec_part2
let value1 = gf.next("to vanish");
let value2 = gf.next(value1);
let value3 = gf.next(value2);

そうすれば、このgeneratorの便利さが実感されるものと思います。
外部変数value1にreturnされた値を入れ、その結果を反映した関数実行をgf.next(value1)で行って、さらにそれは二個目のyieldでvalue2に返却されて。。。と気持ちよく処理が進んでいくわけです。

一応、それを実例で示す為、以下のコードを載せておきます。

generator2.js
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