Promise の withResolvers() メソッドを知りませんでした
Promise
の reolver 関数を外で使いたい
HTML/JavaSrcipt プログラミングの話です。
とある案件で、外部から非同期にセットされるオブジェクトを待って、そのオブジェクトに対する処理を実装する必要が発生しました。
そこで自分は以下のように実装しました。
class Foo {
private resolve: (obj: Bar) => void;
private promise: Promise<Bar>;
constructor() {
// ここで Promise を用意しつつ、resolver 関数を外に取り出す
this.promise = new Promise<Bar>(r => this.resolve = r);
}
setObject(obj: Bar) {
this.resolve(obj);
}
async doSomething() {
// obj が外部からセットされるまで待機
const obj = await this.promise();
// obj を使って何かする
}
}
ここで、ちょっとびみょい実装として、以下の部分があります。
// ここで Promise を用意しつつ、resolver 関数を外に取り出す
this.promise = new Promise<Bar>(r => this.resolve = r);
Promise
のコンストラクタに渡すコールバック関数から、その第1引数を取り出して、それを外部の変数 (ここでは class のプロパティ) に設定して... みたいな感じで、なんだかまどろっこしいですよね。
withResolvers
メソッドが使えるよ
いまは そうしたところ、とある機会に、Promise
には withResolvers
メソッドというものがある、と教えていただきました (出典)。
このメソッドを呼び出せば一発で、promise, resolver, reject の 3者が戻り値で返ってくる、というスグレモノです。
なので、上記コードは以下のように変更できます。
class Foo {
private deferredObj: PromiseWithResolvers<Bar>;
constructor() {
// withResolvers メソッド一発で済む
this.deferredObj = Promise<Bar>.withResolvers();
}
setObject(obj: Bar) {
this.deferredObj.resolve(obj);
}
async doSomething() {
const obj = await this.deferredObj.promise();
}
}
コンストラクタの行数こそ違いはありませんが、コードの意図がすっきり見えてますよね。当初の実装だと、「Promise
コンストラクタの引数の resolve
関数を、単にプロパティに放り込んでいるだけ? なんだこりゃ?」と一瞬迷ってしまうかもしれませんが、withResolvers
はまさしく promise、resolve、reject の3者を用意するよ! ということがすぐにわかります。
プロパティ数も 2 つから 1 つに減りますし、そもそも当初実装のように1つの機能・動作を実現するのに、これら 2 つのプロパティが強力に紐付いているのも気持ちが悪いところです。
withResolvers
メソッドを使うことで、ずいぶん可読性・保守性が上がりますね!
いつから使えるの?
withResolvers
メソッドはやや新しめの機能です。"can I use" で検索してみると、古くは 2023年の10月頃には Chromiium 系や Firefox で、Safari はちょっと遅くて 2024年3月頃 (iOS 17.4 のリリース) から使えるようになったようです。
https://caniuse.com/?search=withResolvers
古い環境のことをあまり心配しなくてよければ、Promise.withResolvers()
は上手に使っていきたいですね。
おわりに
ちゃんと毎年の The State of JS survey に目を通しておくと、モダンな JavaScript について行けますね!
Discussion