TypeScriptでイベントをPromise化する関数の型を定義したい ― 2022年12月03日 10時41分
この記事はTypeScript Advent Calendar 2022の3日目の分です。
「addEventListenerでリッスンしているイベントをPromise化する」という記事で、イベントをPromise
で受け取る関数が紹介されています。Node.jsのevents
モジュールのevents.once
メソッドと同じ機能を実現するものですね。Webブラウザ組み込みのDOMでも同じ機能を提供しようという提案もなされています。
最初の記事では余談として今回紹介したeventPromisifyはTypeScriptで書こうとすると型の定義が難しいなと思いました
と書かれています。例えばeventPromisify(document, 'click')
と呼び出したら返り値の型がPromise<MouseEvent>
になってほしいのですが、そのような型定義を記述できるでしょうか? (以下、TypeScript 4.9を想定しています。)
イベントが発生する対象(target
)とイベント名(type
)を決め打ちできるのなら、
type EventForDocumentClick =
typeof document.addEventListener<'click'> extends
(type: 'click', listener: (event: infer E) => void) => void
? E : never;
// = MouseEvent
のように、document
と'click'
からMouseEvent
を導出できます。しかし、変数document
ではなくDocument
型だけが与えられているとき、Document['addEventListener']<'click'>
のように型引数を指定することはできません。
またDocument
型におけるaddEventListener
メソッドの定義は、イベント名がDocument
固有(keyof DocumentEventMap
型)のものとイベント名が文字列全般(string
型)のものがオーバーロードされています。型引数が絡んでいなければ、「オーバーロードされた関数型から引数の型や返り値の型を取り出す方法」に書かれているように型を取り出せます。しかし、型引数を持つメソッドがオーバーロードされているときに、意図した型引数が指定された場合の引数や返り値の型を取り出せるのかどうか、私にはわかりませんでした。
結局私にできたのは以下の状態までです(TypeScript Playgroundで確認)。
type EventTypeFor<Target extends EventTarget> = Target['addEventListener'] extends {
(type: infer T, listener: (e: Event) => void): void;
(type: string, listener: EventListenerOrEventListenerObject): void;
} ? T : never;
type EventFor<Target extends EventTarget> = Target['addEventListener'] extends {
(type: string, listener: (e: infer E) => void): void;
(type: string, listener: EventListenerOrEventListenerObject): void;
} ? E : never;
const eventPromisify = <T extends EventTarget>(
target: T,
type: EventTypeFor<T>
): Promise<EventFor<T>> => {
throw new Error('Not implemented');
}
const p = eventPromisify(document, 'click');
// p: Promise<Event | MouseEvent | UIEvent | ClipboardEvent | AnimationEvent | InputEvent | FocusEvent | ... 11 more ... | WheelEvent>
第2引数をDocument
固有のイベント名に限定することはできています。しかしながら、返り値のPromise
の値の型をMouseEvent
に限定することはできず、Document
固有のイベントすべての共用体型となってしまいます。
コメント
トラックバック
このエントリのトラックバックURL: http://nanto.asablo.jp/blog/2022/12/03/9545391/tb
コメントをどうぞ
※メールアドレスとURLの入力は必須ではありません。 入力されたメールアドレスは記事に反映されず、ブログの管理者のみが参照できます。
※投稿には管理者が設定した質問に答える必要があります。