constructor で await したい
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ コンストラクタを async 関数にせず Promise を返す
◆ クラス構文は自由に return できないから旧来の方法のみ
◆ コンストラクタの返り値がインスタンスでないちょっと変わった使い方になる
◆ ready プロパティに Promise を入れる
◆ 使う側が await instance.ready をする必要がある
◆ 他は同期的に使えるしクラス構文にも対応できる
◆ 各メソッドが必要な非同期プロパティを await する
◆ 使う側の負担は減るけど作る側が面倒
◆ クラス構文は自由に return できないから旧来の方法のみ
◆ コンストラクタの返り値がインスタンスでないちょっと変わった使い方になる
◆ ready プロパティに Promise を入れる
◆ 使う側が await instance.ready をする必要がある
◆ 他は同期的に使えるしクラス構文にも対応できる
◆ 各メソッドが必要な非同期プロパティを await する
◆ 使う側の負担は減るけど作る側が面倒
コンストラクタで設定するプロパティに async 関数の結果を入れたいことってありますよね
これで行けるかなと思ったのですが
TypeError: A is not a constructor
となります
となって async 関数にするとコンストラクタとして (new を使って) 実行することができなくなります
そうまでしなくても単純に async 関数にせず Promise を返すという手もあります
new は絶対その型のインスタンスを返すべきという考えもありそうです
私は別に気にしてませんけど
また class 構文を使う場合はいろいろ制限がつくので上のこの方法は使えません
返り値は自由にできず this のプロパティ設定くらいしか constructor でできません
そういう場合はプロパティに Promise を入れるといいと思います
プロパティがひとつだけなら Promise コンストラクタを使わずに
ともできます
使うときにこういう複雑なつくりになります
コンストラクタの返り値や どこかに Promise を入れてそれを絶対に待ってから処理するという前提じゃないと 非同期処理が終わってるか判断がつかないので使用箇所全部でこういう使い方にしないといけません
getter で工夫しても await がいることには変わりありません
のように使えます
ただ xxx の処理では内部的に通信など非同期処理があります
しかし それを気にせず使えます
内部でメソッドを実行しても非同期処理の初期化が完了してなければ待ってから実行されるようです
プロパティに入れたいものや そのクラスそのものをそういう作りにしてしまうということもできます
簡単な方法だと上に書いた promise をプロパティに入れる方法を使って 非同期で取得するプロパティを使うそれぞれのメソッドが最初に await します
作る側がちょっと面倒ですが 使う側が毎回最初に await しなくて済むというメリットもあります
インスタンス作成時に プロパティを await しないとけないというのは忘れやすいですしバグのもとになりそうなので メソッド内で await するか new の返り値自体を Promise 値にするのどっちかでいいかと思います
function delayValue(value, n) {
return new Promise(resolve => setTimeout(() => resolve(value), n))
}
async function A() {
this.x = await delayValue("a", 1000)
}
const a = await new A()
これで行けるかなと思ったのですが
TypeError: A is not a constructor
となります
(function(){}).prototype
// {constructor: ƒ}
(async function(){}).prototype
// undefined
となって async 関数にするとコンストラクタとして (new を使って) 実行することができなくなります
Promise を返す
JavaScript では別に new を使う必要はないので普通に実行して返り値を新しいインスタンスにするということはできますそうまでしなくても単純に async 関数にせず Promise を返すという手もあります
function delayValue(value, n) {
return new Promise(resolve => setTimeout(() => resolve(value), n))
}
function A() {
return new Promise(async resolve => {
this.x = await delayValue("a", 1000)
resolve(this)
})
}
const a = await new A()
console.log(a)
// A {x: "a"}
Promise をプロパティに入れる
new の返り値が Promise というのは人によっては嫌かもしれませんnew は絶対その型のインスタンスを返すべきという考えもありそうです
私は別に気にしてませんけど
また class 構文を使う場合はいろいろ制限がつくので上のこの方法は使えません
返り値は自由にできず this のプロパティ設定くらいしか constructor でできません
そういう場合はプロパティに Promise を入れるといいと思います
const a = new class {
constructor() {
this.ready = new Promise(async resolve => {
this.foo = await delayValue("a", 1000)
resolve()
})
}
}()
await a.ready
console.log(a.foo)
// a
プロパティがひとつだけなら Promise コンストラクタを使わずに
constuctor() {
this.ready = xxx().then(x => {
this.foo = x
})
}
ともできます
特別なことをしないと
普通にプロパティに Promise のまま入れておけばと思うかもしれませんが そうするとawait (await this.foo).bar("baz")
使うときにこういう複雑なつくりになります
コンストラクタの返り値や どこかに Promise を入れてそれを絶対に待ってから処理するという前提じゃないと 非同期処理が終わってるか判断がつかないので使用箇所全部でこういう使い方にしないといけません
getter で工夫しても await がいることには変わりありません
非同期処理側で対処
中には非同期でプロパティに設定したいオブジェクトを作るライブラリ側で対処してくれているものもありますfunction A() {
this.foo = xxx()
}
console.log(await new A().foo.bar())
// a
のように使えます
ただ xxx の処理では内部的に通信など非同期処理があります
しかし それを気にせず使えます
内部でメソッドを実行しても非同期処理の初期化が完了してなければ待ってから実行されるようです
プロパティに入れたいものや そのクラスそのものをそういう作りにしてしまうということもできます
簡単な方法だと上に書いた promise をプロパティに入れる方法を使って 非同期で取得するプロパティを使うそれぞれのメソッドが最初に await します
作る側がちょっと面倒ですが 使う側が毎回最初に await しなくて済むというメリットもあります
インスタンス作成時に プロパティを await しないとけないというのは忘れやすいですしバグのもとになりそうなので メソッド内で await するか new の返り値自体を Promise 値にするのどっちかでいいかと思います