【javascript基礎講座】クラスコンストラクタの内部で async/await を使う時の注意


2020/06/29

サードパーティ製のnpmライブラリを利用する際に返り値が
Promise<T>のメソッドが結構見受けられます。

これを自作のクラスのコンストラクタで利用するとなるとひと工夫必要です。

今回はPromiseから返ってくる値でクラスのメンバー変数を初期化するための、ちょっとしたテクニックを解説します。


コンストラクタにasyncは付けられない

まず以下はダメなパターンです。

            
            class HogeClass {
    async constructor(hoge) {
        // Promiseを返すなんらかのメソッド
        this.hogeContent = await initHogeAsync(hoge);
    }
}

//...

const hogeInstance = new HogeClass('hoge'); // Error!
        
このソースコードは通常ビルドエラーとなるか、仮にコンパイルを通過してしてしまったとしても内部エラーで停まります。

これはJavascriptのコンストラクタは通常実体のあるオブジェクトを返す関数である必要があるためです。

一方で
asyncが修飾される場合にはPromiseインターフェイスを返す関数になり、呼び出した時点では中身の無い値へのプロキシです。

この場合、コンストラクタは
asyncをつけることが本質的にできない訳です。

...ということは、コンストラクタで
awaitは原則として利用できない...?

という困った場合に使えるテクニックを以降で紹介していきます。


async/awaitの初期化で困った時のFactoryライクなメソッド

先ほどの述べたように、問題は単純で、コンストラクタをasyncな関数にできない、ということでした。

ですのでコンストラクタのようでコンストラクタではない、staticな関数を追加しましょう。

            
            class HogeClass {
    // こちらのコンストラクタにはawaitで呼び出ししない
    constructor() {}

    static async hogeFactorywise(hoge) {
        const obj = new Hoge();
        // Promiseを返すなんらかのメソッド
        obj.hogeContent = await initHogeAsync(hoge);
        return obj;
    }
}

//...

const hogeInstance = await HogeClass.hogeFacorywise('hoge');
        
コンストラクタでなければ、async指定することが可能になります。

コンストラクタを呼び出してインスタンス化したオブジェクトで、Promiseを返すメソッドから値の受け渡しを行うことができるようになります。


応用例 ~ Tensorflowjsのメソッド

以上のようなテクニックを取り上げたのも、Tensorflowjsの関数は主にPromise<T>で値が返ってくるものが多いようなので、個人的にまとめておきたかったというのもあります。

代表的なものだと
loadLayerModelメソッドで、訓練済みデータを読み込んでモデルを復元して返す関数があります。

公式のAPIリファレンスにもあるように、暗黙としてPromiseを返すので、
awaitで受けるのが通例のようです。

            
            const model = await tf.loadLayersModel(
    'https://storage.googleapis.com/tfjs-models/tfjs/iris_v1/model.json'
);
model.summary();
        
訓練済みのTensorflowjsの機械学習モデルをクラス変数として持ちたい場合には、コンストラクタでの初期化はできないので、上記のテクニックを利用します。

一例をあげると、AngularのSPAプロジェクト仕立てにしているのでtypescriptコードですが、中身はほぼjavascriptですので、読み替えていただくとして...

            
            import * as tf from '@tensorflow/tfjs';

export class HogeAsyncClass {
    config: any;
    decodedLayerModel: any;

    /**
     * staticなfactoryのような関数はconstructorの前に定義することが
     * 作法として推奨されいるようなので、この位置でメソッド定義
     */
    static async hogeFactorywise(args?: any): Promise<HogeAsyncClass> {
        const hogeObj = new HogeAsyncClass(args);
        // 👇Promiseから取り出したデータで初期化している
        hogeObj.decodedLayerModel = await tf.loadLayersModel('<...モデルを保存した場所までのディレクトリパス>/model.json');
        return hogeObj;
    }

    constructor(args?: any) {
        this.config = args.config;
    }
}
        
あとは使う側で、

            
            const hogeInstance = await HogeAsyncClass.hogeFacorywise(<...通常のコンストラクタ引数>);
        
として扱うことが可能です。


まとめ

Promiseは使い慣れてくると便利です。

これからTensorflowjsを利用したいと思っている方からすると、「これはなんで思うように動かない...?」と感じるかもしれません。

javascriptで機械学習を始める際には、Promiseの挙動はしっかりと押さえておきましょう。
記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。