3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【TypeScript】コンストラクタ内で、非同期処理で取得した値をインスタンス変数に設定する方法

Posted at

はじめに

こんにちは!
タイトルのケースを実装する際に若干迷ったので記事にします。
同じようなことをしたい方の参考になればと思います!

問題

以下のケースの場合を考えます。

例.NGパターン

index.ts
// クラス
class SampleClass {
  private asyncVar: number | undefined;

  // 初期化
  constructor() {
    const _this = this;
    setTimeout(() => {
      _this.asyncVar = 1; // 非同期関数(setTimeout)内でインスタンス変数を初期化
    }, 1);
  }

  // 出力
  print() {
    console.log(this.asyncVar);
  }
}

// インスタンス化
const instance = new SampleClass();
// 出力
instance.print(); // undefined

やりたいこととしては、SampleClassのコンストラクタ内で初期化したasyncVarの値(1)を、print関数でコンソールに出力したいとします。
が、コメントにある通りこのソースではprint関数にて1が出力されず、undefinedが出力されてしまいます。
これは、まず同期処理が全て実行されてから非同期の処理が実行されるというJavaScriptの仕様のためです(詳細は↓の記事が分かりやすかった)。つまり、SampleClassのインスタンス化が実行されてからprint関数が実行され、最後にコンストラクタ内のsetTimeoutで指定したコールバック関数が実行されるという流れで処理が進むため、print関数が実行される時点では変数が初期化されておらず、undefinedが表示されてしまうということですね。

ちなみに、コンストラクタ関数でasync constructor() {のような記述はできず、処理を待つようにすることはできません。
ではどのようにasyncVarを初期化するか、↓に記載します。

解決方法

答えは、インスタンス化を行うstaticメソッドを定義し、その中でインスタンス変数を初期化するです。
具体的には以下のソースの通りです。

OKパターン

index.ts
// クラス
class SampleClass {
  private asyncVar: number | undefined;

  // 初期化
+  private constructor() {}

+  // 生成
+  static async create() {
+    const instance = new SampleClass();
+    await instance._initialize();
+    return instance;
+  }
+
+  // インスタンス変数の初期化
+  private async _initialize() {
+    const _this = this;
+    await new Promise((resolve) => {
+      setTimeout(() => {
+        _this.asyncVar = 1; // 非同期関数(setTimeout)内でインスタンス変数を初期化
+        resolve(true);
+      }, 1);
+    });
+  }

  // 出力
  print() {
    console.log(this.asyncVar);
  }
}

// インスタンス化
+ const instance = await SampleClass.create();
// 出力
instance.print(); // 1

ポイントは constructorをprivate化していること と、 create関数内で、生成したインスタンスを返却していること です。まず、constructorをprivate化することで通常のnewを禁止し、インスタンスを生成する場合には必ずcreate関数を経由することを強制しています。
こうすることで、create関数内で呼び出している_initialize関数の中で変数の初期化を待ってから生成したインスタンスを返却させることができます(_initialize関数の中では、awaitを使ってasyncVarの初期化を待たせています)。

上述の通り、インスタンス化はnew SampleClass()ではなく、await SampleClass.create()で行います。こうすることで、インスタンス変数の初期化を待つことができるので、print関数の出力結果が1となり想定通りに出力されることになります。

おわりに

JavaScriptは非同期とthisの扱いが鬼門ですが、ここを乗り越えたら使いこなしたと言えると思います(個人的意見)。

JISOUのメンバー募集中🔥

プログラミングコーチングJISOUではメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか?
気になる方はぜひHPからライン登録お願いします!👇

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?