TypescriptのInterfaceでasync関数を定義する・async関数でクラス変数(this)を使う


2022/05/13

Typescriptで自作interfaceからclassを定義する際に、クラスのメンバーメソッドにasyncを付けて使いたいときがあります。

今回はTypescriptのinterfaceとclassに着目して、async関数をどう扱うか簡単に紹介します。


TypescriptのInterfaceに非同期関数を定義する

まずはインターフェース側からasync関数を実装する例を以下に示します。

            
            interface IHoge {
    name: string;
    id: number;
    getUserInfo(): Promise<any>
}
        
...asyncは何処にも無いではないか?と思わたかも知れません。でもこれでインターフェース側はOKです。

ポイントとしてTypescriptのinterfaceで定義するメソッドにはそもそも
asyncを直接付けられないのですが、asycn関数の実体はPromise<T>を返す関数ですので、Typescriptのインターフェースではasyncを付けなくても良いことになります。


クラス内asyncメソッドからメンバー変数をthisを使って呼び出す

で次に、先程のインターフェースを何かクラスに実装して使う方もやってみましょう。

ここでの注意点として、Javascriptの
thisは色々と混乱を招きやすいため、通常関数(function)を使って呼び出すのと、アロー関数でやるとのでは挙動が異なることが良く知られているかと思います。

Typescriptから変換したJavascriptの影響を受け、例えば以下のダメなコードをトランスパイルして実行すると、
this.namethis.idがUndefinedエラーを吐いて処理は正常終了しないときがあります。

            
            class HogeUser implements IHoge {
    name: string;
    id: number;

    constructor(_name: string, _id: number) {
        this.name = _name;
        this.id = _id;
    }

    async getUserInfo(): Promise<any> {
        const res = await fetch(`http://hoge.hoge.com/api?uname=${this.name}&uid=${this.id}`);
        const data = await res.json();
        return data;
    }
}
        

これは通常の関数呼び出しだと、クラス内に定義した関数でさえ
thisがグローバルスコープの方を読みにいくため、「グローバルのthisにそんな変数はいない」とエラーを吐いてくる訳です。

ただし、JavascriptのES6以降のclass構文をそのまま使った以下のような実装だと、通常関数からでも
thisをクラス内スコープとして取り扱ってくれます。

            
            class HogeUser {
    constructor(name, id) {
        this.name = name;
        this.id = id;
    }

    async getUserInfo() {
        const res = await fetch(`http://hoge.hoge.com/api?uname=${this.name}&uid=${this.id}`);
        const data = await res.json();
        return data;
    }
}
        

...もともとあった
Typesciptでのclass構文と、後で追加された本家Javacriptのclass構文でも、細かく見ると別物だということが分かります。

classを使う時は、TypescriptかJavascriptかで厳密に区別して使うことが必要です...なんだかややこしいなぁ。

ともかくTypesciprでどうすればよいかというと、少し気持ちが悪くはありますが、
「クラスメソッドのアロー関数」化で対処します。

以下のコードであれば、Typescriptのclassでasyncを使うことができます。

            
            class HogeUser implements IHoge {
    name: string;
    id: number;

    constructor(_name: string, _id: number) {
        this.name = _name;
        this.id = _id;
    }

    getUserInfo = async (): Promise<any> => {
        const res = await fetch(`http://hoge.hoge.com/api?uname=${this.name}&uid=${this.id}`);
        const data = await res.json();
        return data;
    }
}
        

これでクラス内の
thisをクラスメソッド内からも参照できて、インターフェースの実装からも怒られない書き方になります。めでたしめでたし。

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

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