JavaScript
TypeScript

Javascript の yield と generator

TypeScript を学習中で、非同期の挙動が良く理解できていないことがわかった。その挙動を理解するために、TypeScript の async/await が JavaScriptにコンパイルされる際に登場する、Generator と yield について意味を調べてみた。

はじめのサンプル

yield Generator function をポーズしたり、レジュームしたりするのに使われるキーワード。具体例を見てみよう。

function* generator(age){
    console.log("Hi");
    yield;
    console.log("My name is ");  // point 2
    yield;
    console.log("Tsuyoshi.");
    yield;
    console.log(`age ${age}`);
}

let g = generator(46); // point 1

console.log(g.next()); // point 3
console.log(g.next());
console.log(g.next());

実行結果

Hi
{ value: undefined, done: false }
My name is
{ value: undefined, done: false }
Tsuyoshi.
{ value: undefined, done: false }

function* で定義されるのが、Generator function と呼ばれるもので、Generator Object を返却する特殊な関数だ。Generator Object は、iterable protocol と iterator protocolを持っている、Generator function から返却されるオブジェクト。

yield の振る舞いの要点

ポイントとしては、この関数を実行すると、Generator Object が帰ってきて(point 1) その時点では、最初のyield句までしか、関数が実行されておらず、関数の実行が、yield句の箇所で停止(point 2)したようになっている。次に、返却されたGenerator Object の、next() メソッドをコールすると再び、yield 句のところから実行される。実行結果を見ても、制御が呼び元と、Generator 関数を行き来しているのがわかるだろう。

 通常の関数実行だと制御は次のようになる。

呼び出し元 -> 関数 -> 呼び出し元

 Generator function と yieldを使うと

呼び出し元 -> Generator 関数 yield まで -> 呼び出し元 next() まで -> Generator 関数 次の yield まで -> 呼び出し元 次のnext() 

yield からの値の返却

yield に引数を持たせることもできる。呼び出し元のnext()でその値が返却される。

function* generator(age){
    console.log("Hi");
    yield 1;
    console.log("My name is ");
    yield "hello";
    console.log("Tsuyoshi.");
    yield 2;
    console.log(`age ${age}`);
}

let g = generator(46);

console.log(g.next());
console.log(g.next());
console.log(g.next());
Hi
{ value: 1, done: false }
My name is
{ value: 'hello', done: false }
Tsuyoshi.
{ value: 2, done: false }

Generator Object を for で回す

ちなみに、Generator Object は、イテレーターということなので、試しに
for 文でうまく書けないか試した見たら、この方法だったらいい感じみたい。

function* generator(age){
    console.log("Hi");
    yield 1;
    console.log("My name is ");
    yield "hello";
    console.log("Tsuyoshi.");
    yield 2;
    console.log(`age ${age}`);
}

let g = generator(46);

for (gen of g) {
    console.log(gen);
}

Hi
1
My name is
hello
Tsuyoshi.
2
age 46

結果が違うのは、先のプログラムだと、Generator Object の next() は、値と、イテレーターが終了するかの属性のあるオブジェクトを返却しているのに対して、for ( ... of ... )の構文だと、純粋な戻りの値のみになっているためである。

さて、これで、TypeScript の async/await の仕組みに対して言及する準備ができた!