1. ホーム
  2. ジャバスクリプト

[解決済み】非同期/待機クラス コンストラクタ

2022-03-31 14:37:54

質問

今現在、私は async/await をクラスのコンストラクタ関数内で使用します。 これは、カスタム e-mail タグを使用しています。

customElements.define('e-mail', class extends HTMLElement {
  async constructor() {
    super()

    let uid = this.getAttribute('data-uid')
    let message = await grabUID(uid)

    const shadowRoot = this.attachShadow({mode: 'open'})
    shadowRoot.innerHTML = `
      <div id="email">A random email message has appeared. ${message}</div>
    `
  }
})

しかし、現時点では、以下のようなエラーが出て、プロジェクトはうまくいきません。

Class constructor may not be an async method

これを回避して、この中でasync/awaitを使えるようにする方法はないでしょうか? コールバックや.then()を必要とする代わりに?

解決方法は?

これは 決して が動作します。

は、その async キーワードによって await とマークされた関数で使用されます。 async が、その関数をプロミスジェネレータに変換してくれるのです。ですから async はプロミスを返します。一方、コンストラクタは構築中のオブジェクトを返します。このように、オブジェクトとプロミスの両方を返したいという状況は、ありえないことなのです。

async/awaitは基本的にプロミスのためのシンタックスシュガーなので、プロミスが使える場所でしか使うことができません。コンストラクタはプロミスではなく、構築されるオブジェクトを返さなければならないので、コンストラクタでプロミスを使用することはできません。

これを克服するために2つのデザインパターンがありますが、どちらもプロミスが登場する前に考案されたものです。

  1. を使用する。 init() 関数を使用します。これは、jQueryの .ready() . 作成したオブジェクトは、そのオブジェクトの内部でのみ使用できます。 init または ready 関数を使用します。

    使用方法を説明します。

    var myObj = new myClass();
    myObj.init(function() {
        // inside here you can use myObj
    });
    
    

    インプリメンテーションです。

    class myClass {
        constructor () {
    
        }
    
        init (callback) {
            // do something async and call the callback:
            callback.bind(this)();
        }
    }
    
    
  2. ビルダーを使用する。javascriptでこれが使われているのをあまり見たことがありませんが、オブジェクトを非同期に構築する必要がある場合、これはJavaでより一般的な回避策の1つです。もちろん、ビルダーパターンが使われるのは、多くの複雑なパラメータを必要とするオブジェクトを構築するときです。これこそ、非同期ビルダーのユースケースです。違いは、非同期ビルダーはオブジェクトを返すのではなく、そのオブジェクトのプロミスを返すことです。

    使用方法

    myClass.build().then(function(myObj) {
        // myObj is returned by the promise, 
        // not by the constructor
        // or builder
    });
    
    // with async/await:
    
    async function foo () {
        var myObj = await myClass.build();
    }
    
    

    インプリメンテーションです。

    class myClass {
        constructor (async_param) {
            if (typeof async_param === 'undefined') {
                throw new Error('Cannot be called directly');
            }
        }
    
        static build () {
            return doSomeAsyncStuff()
               .then(function(async_result){
                   return new myClass(async_result);
               });
        }
    }
    
    

    async/awaitを使った実装。

    class myClass {
        constructor (async_param) {
            if (typeof async_param === 'undefined') {
                throw new Error('Cannot be called directly');
            }
        }
    
        static async build () {
            var async_result = await doSomeAsyncStuff();
            return new myClass(async_result);
        }
    }
    
    

注意:上記の例では、非同期ビルダーにプロミスを使用していますが、厳密には必要ではありません。コールバックを受け取るビルダーも同様に簡単に書くことができます。


静的関数の内部で関数を呼び出す場合の注意点。

これは非同期コンストラクタとは全く関係なく、キーワードである this を必要としない言語から来た人々にとっては、少し驚きかもしれません。 this というキーワードがあります)。

は、その this キーワードは、インスタンス化されたオブジェクトを指します。クラスではありません。したがって、通常 this なぜなら、スタティック関数はどのオブジェクトにも束縛されず、直接クラスに束縛されるからです。

つまり、次のようなコードで。

class A {
    static foo () {}
}

することはできません。

var a = new A();
a.foo() // NOPE!!

として呼び出す必要があります。

A.foo();

したがって、次のようなコードはエラーになります。

class A {
    static foo () {
        this.bar(); // you are calling this as static
                    // so bar is undefinned
    }
    bar () {}
}

これを修正するために bar を通常の関数か静的メソッドにすることです。

function bar1 () {}

class A {
    static foo () {
        bar1();   // this is OK
        A.bar2(); // this is OK
    }

    static bar2 () {}
}