非同期処理を含む初期化処理を書くためにinitializeメソッドを生やすとよさそうなのでまとめる

背景

  • JavaScript のコンストラクタは async/await の書き方が使えない
  • 別メソッド呼べば?という解法は紹介されている

javascript

  • Async/Await Class Constructor - Stack Overflow</a>

自分なりのまとめ

基本

コンストラクタは使ってもよいが initialize を呼ぶ方が便利になるように作る。コンストラクタの中から別メソッドを呼ぶのではなく、初期化そのものを

await Klass.initialize(..)

で行えるようにする。基本はこんな感じ。

class Klass {
  constructor ({ arg1, arg2, .. }) {
    this.prop1 = arg1
    this.prop2 = args2
  }

  static async initialize (args) {
    ..
    const foo = await ExternalHeavyProcess(..)
    ..
    return new this(ARGS)
  }
}

ポイントは コンストラクタから見たときには DI になるようにすること。

こうすることで依存先を独立してテストしつつ、テストの際に依存ライブラリを差し替えたりパラメータを変更したりすることを容易にしておく。

全部開いて書くとこんな感じになる。

class Klass {
  constructor ({ arg1, arg2, .. }) {
    this.prop1 = arg1
    this.prop2 = args2
  }

  static async initialize ({
    arg1,
    arg2 = await ExternalHeavyProcess1(..),
    arg3 = await ExternalHeavyProcess2(..),
    ..
  }) {
    ..
    some processes
    ..
    return new this({ args1, arg2, foo, bar, .. })
  }
}

正直言うと長い。どうしても initialize の中が厚くなってしまう。とは言え、testability を確保しつつ async function を使いつつ class からオブジェクトを生成しようと思うと、おそらくこれくらいが現状はベストだと思う。

自身を返すか他者を返すか

初期化の処理が多くなりすぎたりオブジェクトの生成にしか直接関係しない(例えば Service Locator 周りの)コードが膨らんだりすると、実際のオブジェクトの役割が分かりにくくなってしまうので、そういう場合は

GRASP (object-oriented design) - Wikipedia

にしたがって creator に分離するとよいだろう。

一つのオブジェクトに対して二つの class が関係するのは高コストに見えるかもしれないが、根本的に高コストなオブジェクトを生成しているのだから仕方がない。高コストなことが可視化されただけ。

その場合は以下のような感じ。

class Klass {
  constructor (args)
}

class KlassCreator {
  static async create (..) {
    await method1ArroundCreation(..)
    await method2ArroundCreation(..)

    return new Klass(args)
  }

  static method1ArroundCreation (..) {
  }

  static method2ArroundCreation (..) {
  }
}

More