アヒルのある日

株式会社AHIRUの社員ブログです。毎週更新!社員が自由に思いついたことを書きます。

javascriptでコルーチン

こんにちは、みにくい社長です。
最近久々にjavascriptを触っているのですが、Unityで使えるコルーチンのような記述方法を調べてもなかなか見つからなかったので、解説してみようと思います。 具体的には、

private IEnumerator timer()
{
    Console.WriteLine("開始");
    int time = 0;
    while(true)
    {
        yield return new WaitForSeconds(1.0f);
        time++;
        Console.WriteLine($"{time}秒経過");
    }
}

StartCoroutine(timer());

というようなものです。UnityでもMonoBehaviour継承でないと使えないのですが、非常に便利な機能です。
まずjavascriptの非同期処理の書き方が色々あるので混乱したのですが、コルーチン処理を書くにはasync/awaitを使うのが良さそうです。
上記のコードをjavascriptにすると下記のように書くことができます。

var timer = async function() {
    console.log("開始");
    var time = 0;
    while(true)
    {
        await new Promise(resolve => setTimeout(resolve, 1000));
        time++;
        console.log(time + "秒経過");
    }
}

timer();

async/awaitについては、IEnumerator/yield returnに対応しているのでわかりやすいかと思います。 Promiseについてはjavascriptの機能で、resolve関数を実行すると完了になるオブジェクトです。 今回は1秒待つということをするために、setTimeoutを使って1秒後にresolve関数を実行するという形で実現しています。
では、秒数を待つのではなく入力待ちなどはどのようにすれば良いでしょう。 例えばUnityで下記のようなwindowを閉じるまで待つケースで考えてみます。

bool close = false;
window.Open(() => {
    close = true;
});
yield return new WaitUntil(() => { return close; });

これをjavascriptで書くために、waitPromiseという関数を用意します。

function waitPromise(conditionFunction, msec) {
    return new Promise((resolve) => {
        var __PROMISE_FUNC__ = () => {
            if (conditionFunction()) {
                resolve();
            }
            else {
                setTimeout(__PROMISE_FUNC__, msec);
            }
        };
        setTimeout(__PROMISE_FUNC__, msec);
    });
}

条件判定関数をmsec毎に呼び出し、条件を満たしたらresolve、満たさなければsetTimeoutで繰り返し呼び出します。
使い方はこのようにUnityに近い形になります。

var close = false;
window.open(function () {
    close = true;
});
await waitPromise(() => { return close; }, 100);

これで便利なUnityのコルーチンと同じ処理ができますね。
それでは、またねー!