JavaScriptのyieldを使って関数の実行を一時停止する

プログラミング

プログラムはできるだけシンプルに分かりやすく記述することで、バグや認識違いを少なくすることができます。

JavaScriptのyieldは、関数の実行を一時停止することで複雑な非同期処理を簡単に同期処理にさせることができます。

本記事では、JavaScriptのyieldについて解説します。

記事内に記載しているプログラムは、Node.js14.14.0を使って動作確認をしています。

JavaScriptのyieldを使って関数の実行を一時停止する

JavaScriptのyieldについて、以下の内容を解説します。

  • JavaScriptのyieldとは
  • yieldの基本的な使い方
  • yieldへの引数渡し、値渡し
  • ジェネレータ関数をまとめるyield
  • yieldを使った同期処理

JavaScriptのyieldとは

yieldという英単語を辞書で調べると、「生産する」「譲る」「取って代わる」などの意味がある単語となります。

JavaScriptにおけるyieldを単語の意味に合わせて言うならば、関数の処理を一時的に呼び出し元に譲って、関数の処理は待機しておくという意味になるかと思います。
もう少しプログラム的に説明すると、関数のyieldの場所で処理を一旦停止して戻り値を返し、再度関数が呼ばれると、続きからスタートする、レジューム機能のようなものとなります。

上述の通り、yieldは関数内で使用され、yieldを含む関数をジェネレータ関数と呼びます。
yieldには、関数の戻り値となる値を指定します。

また、関数内にいくつもyieldを入れることができます。
多くの場合はループの処理にて、一旦停止させるような場面に使われることが多いです。

yieldを使うメリットは、一時的に処理を停止することで、遅延評価を行ったり、非同期処理を同期処理へと変化させたりできることです。

yieldの基本的な使い方

yieldの基本的な使い方は、以下の通りです。

function* funcA() {
    yield "Hello";
    yield "JavaScript";
    return "!!";
}

f1 = funcA();
console.log(f1.next()); // -> { value: 'Hello', done: false }
console.log(f1.next()); // -> { value: 'JavaScript', done: false }
console.log(f1.next()); // -> { value: '!!', done: true }
console.log(f1.next()); // -> { value: undefined, done: true }

まず、yieldが含まれるジェネレータ関数には、functionの最後にアスタリスクをつける必要があります。
yieldには戻り値となる値を指定します。

そして、ジェネレータ関数を呼び出す側では、nextを使ってジェネレータ関数を実行します。

1回目のnextで、1つ目のyieldまでが実行され、yieldに指定した戻り値が返されます。
2回目のnextで、2つ目のyieldまでが実行され、yieldに指定した戻り値が返されます。

戻り値は、valueとdoneを持つオブジェクトで返されます。
valueはyieldに指定した戻り値で、doneはジェネレータ関数が終了したかどうかを表す真偽値です。

doneがfalseの場合は、もう一度呼び出すと、ジェネレータ関数の途中から実行され、次のyieldもしくは関数の最後まで実行されます。
doneがtrueの場合にもう一度呼び出しても、ジェネレータ関数は実行されず、valueにはundefinedが返ってきます。

yieldへの引数渡し、値渡し

yieldを含むジェネレータ関数には、通常の関数と同様に引数を渡すことができます。
渡した値は、一時停止後も利用可能な値です。

function* funcB(n) {
    yield n;
    n++
    yield n;
    n **= 2;
    yield n;
}

f3 = funcB(3);
console.log(f3.next()) // -> { value: 3, done: false }
console.log(f3.next()) // -> { value: 4, done: false }
console.log(f3.next()) // -> { value: 16, done: false }

上記のプログラムでは、ジェネレータ関数生成時に3を渡し、ジェネレータ関数内で引数を使って様々な計算をしています。

 

また、ジェネレータ関数実行時のnextに引数を指定すると、yieldに渡すことができます。

function* funcC() {
    a = yield 1;
    b = yield 2;
    yield `${a} ${b}`;
}

f4 = funcC();
console.log(f4.next()); // -> { value: 1, done: false }
console.log(f4.next("Hello")); // -> { value: 2, done: false }
console.log(f4.next("JavaScript")); // -> { value: 'Hello JavaScript', done: false }

上記のプログラムでは、2回目と3回目のnextに引数を指定しています。

1回目のnextでは、1を返すところまでが実行されます。
2回目のnextで、aに値を代入するところから始まり、2を返すところまで実行されます。
3回目のnextで、bに値を代入するところから始まり、aとbを結合した文字列を返すところまで実行されます。

このようにすることで、関数の途中で値を渡すような処理も実行することが出来るようになります。

ジェネレータ関数をまとめるyield

yieldを含むジェネレータ関数は、ジェネレータ関数をまとめたジェネレータ関数を作ることができます。
例えば以下のようなものです。

function* oddNumberGenerator() {
    for (var i = 1; i <= 10; i++) {
        if (i % 2 == 1) {
            yield i;
        }
    }
}

function* evenNumberGenerator() {
    for (var i = 1; i <= 10; i++) {
        if (i % 2 == 0) {
            yield i;
        }
    }
}

function* funcD() {
    yield* oddNumberGenerator();
    yield* evenNumberGenerator();
}

f5 = funcD();
for (v of f5) {
    console.log(v);
}

上記のプログラムでは、oddNumberGeneratorとevenNumberGeneratorとまとめたジェネレータ関数funcDを作成しています。
ジェネレータ関数内で別のジェネレータ関数を指定する際は、yieldの後にアスタリスクをつけます。

このfuncDを実行すると、最初にoddNumberGeneratorが実行されて奇数が表示され、その後にevenNumberGeneratorが実行されて偶数が表示される結果となります。

yieldを使った同期処理

yieldを使うことにより、非同期処理によって並列で実行される処理も、途中で関数を止めることで同期しながら処理を実行することが出来るようになります。
例えば以下では、非同期処理をyieldを使って同期処理にしています。

function* funcE() {
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("5s sleep.");
            resolve();
        }, 5000);
    });
    yield new Promise((resolve, reject) => {
        setTimeout(() => {
            console.log("10s sleep.");
            resolve();
        }, 10000);
    });
}

f6 = funcE();
function loop(g) {
    var p = g.next();
    if (p.done) return;
    p.value.then(() => {
        loop(g);
    });
}
loop(f6);

通常の関数として作成して実行すると、5秒のスリープと10秒のスリープが並列で実行されます。
それをyieldを使って一旦停止し、5秒のスリープ結果を評価してから10秒のスリープを開始するように同期をとって処理を実行しています。

昔はyieldを使うことで、callback地獄を防ぐ手段として使われることもありましたが、現在はasync/awaitが一般的ですので、同期処理にするためだけにyieldを用いることはあまりないかもしれません。
ただ、必要なときまで実行を遅らせる遅延評価のような使い方はできますので、使いどころによっては便利な機能となります。

まとめ

JavaScriptのyieldについてまとめると、以下となります。

  • yieldとは、関数の処理を一時的に呼び出し元に譲って(戻して)、自分は待機しておくレジューム機能のようなものである。
  • ジェネレータ関数に引数を渡したり、yieldに値を渡したりすることもできる。
  • ジェネレータ関数をまとめたジェネレータ関数を作ることもできる。

yieldを使うことで複雑な値の生成を、ジェネレータ関数にすることができて、シンプルなプログラムになりますので使ってみてはいかがでしょうか。

 

今回は、JavaScriptのyieldについて解説しました。

以上、参考になれば幸いです。

コメント

タイトルとURLをコピーしました