TypeScript 教學: 建構 EventEmitter/EventTarget 類的定義式

 前言

TypeScript 是一套強大的工具,能夠幫助我們描述與定義 function signature,甚至還可以做到當呼叫函式時,指定其中一項參數值,另一個相依參數的型態 (Type) 就能被 TypeScript 自動推論 (Infer) 出來,接下來就以 NodeJSEventEmitter 為例,示範如何做到。

本文重點:

  1. 了解如何使用 TypeScript 去表達函式參數間的相依關係
  2. 善用 Type AliasMapped Types 等技巧,減少TypeScript 重覆與累贅的定義
  3. 了解 TypeScript 在自動推論可能會帶來的限制,與因應之道

範例一

我們先定義一個 interface 出來,有 2 個屬性 (Properties),分別為 trigger & on,定義如下: 
// interface.ts
export interface EventHandler {
trigger: (event: string, args: Record<string, unknown>) => void
on: (
event: string,
callback: (args: Record<string, unknown>) => void
) => void
}
上面的 TypeScript Definition 定義了以下幾件事。
  1. EventHandler 描述某個物件的"形狀" (Shape)。
  2. 該物件必需要有 triggeron 這 2 個屬性。
  3. trigger 必需是一個 function,接受 2 個參數,第一個型別是 string,第二個型別是個 object
  4. Record<string, unknown> 表示該物件可以有任意屬性名稱,且屬性值是"未知"型別。
  5. on 必需是一個 function type,接受 2 個參數,第一個型別是 string,第二個型別是個 ojbect
  6. unknown anysafe type
我們可以重覆利用 ontriggerargs 參數的定義:
type IndexableObject = Record<string, unknown>

export interface EventHandler {
trigger: (event: string, args: IndexableObject) => void
on: (event: string, callback: (args: IndexableObject) => void) => void
}
這裡宣告了 Type Alias IndexableObject,它是 Record<string,unknown> 的別名。

接著我們引用這個 interface.ts :
// index.ts
import { EventEmitter } from 'events'
import { EventHandler } from './interface'

function getMyEventEmitter(): EventHandler {
const emitter = new EventEmitter()
const myEventEmitter: EventHandler = {
trigger(event, args) {
emitter.emit(event, args)
},
on(event, callback) {
emitter.on(event, callback)
},
}

return myEventEmitter
}

const myEmitter = getMyEventEmitter()
myEmitter.trigger('click', { data: 1 })

如上程式碼所示,這邊做了幾件事:
  1. 定義 getMyEventEmitter 函式。
  2. 當該函式執行時就會傳回一個其型別為 EventHandler 的物件。
  3. myEmitter 建立了以後,我們就可以呼叫 myEmitter.trigger 函式,vscode 會參考 EventHandler TypeScript Definition 檢查我們有沒有輸入不符合型態的參數。








如果我們將游標移到 args 上,vscode 會提示該 args 的型態,這是因為 myEmitterEventHandler 型態,所以 TypeScript 能推論出 trigger & on 的參數型態出來。
從以上的例子看到,我們已經完成了一個簡單的實作,展示了如何定義常用的 Event Handler 的 方法 ( Method ) 的 TypeScript Definition。

這個範例一的定義有些問題,包括:
  1.  trigger & on 這 2 個函式能夠接受的 event name 的型別太過寬鬆,假設在我們的範例裡,trigger & on 僅能處理 ready, start & stop 三種 event ,但顯然以上定義無法限制開發者輸入這 3 者以外的其它 event name
  2.  trigger & on 函式的第一個參數與第二個參數存在相依關係,換句話說,第一個參數如果是 ready ,那麼第二個參數就會有固定的型別,而且不見得會跟其它 event 一樣。

範例二

為了解決範例一引發的問題,可以用以下定義來處理
// interface.ts
type IndexableObject = Record<string, unknown>

interface ReadyEventArgs {
readyTime: number
}

interface StartEventArgs {
jsonData: string
}

interface StopEventArgs {
result: IndexableObject
}

interface EventHandlerArgs {
ready: ReadyEventArgs
start: StartEventArgs
stop: StopEventArgs
}

type EventHandlerEventNames = keyof EventHandlerArgs

export interface EventHandler {
trigger: <T extends EventHandlerEventNames>(
event: T,
args: EventHandlerArgs[T]
) => void
on: <T extends EventHandlerEventNames>(
event: T,
callback: (args: EventHandlerArgs[T]) => void
) => void
}

以上 interface.ts 定義以下幾件事:
  1. 分別定義三個不同 event (readystart & stop) 在 trigger 函式裡第二個參數型別。
  2. ready event 會觸發(trigger)/接受(listen/on) 帶有 ReadyEventArgs Type 的參數。
  3. start event 會觸發(trigger)/接受(listen/on) 帶有 StartEventArgs Type 的參數。
  4. stop event 會觸發(trigger)/接受(listen/on) 帶有 StopEventArgs Type 的參數。
  5. 例如 ready event 觸發後,會傳遞一個物件,裡頭有一屬性名為 readyTime,其型別為 number
  6. 定義 EventHandlerArgs interface
  7.  EventHandlerArgs interface 的屬性名稱即為我們的 EventHandler 能夠觸發與傾聽的 Event Names
  8. EventHandlerArgs interface 的屬性值即為對應 Event Nametrigger 函式裡的第二個參數型別。
  9. EventHandlerArgs interface 的屬性值即為對應 Event Name 於 on 函式裡第二個參數 ( callback 函式型別 ) 裡的第一個參數型別。
總結意思就是,當呼叫 ready event 時,會傳遞的參數會是 ReadyEventArgs Type,且當透過 on 函式註冊傾聽 ready 事件的 event callback 時,該 event callback 收到的參數也會是 ReadyEventArgs Type。依此類推。

為了讓 triggeron 函式能夠對它們的函式參數間產生相依關係,同時會依據之後開發者呼叫這些函式時,帶入的 event name ,決定其它參數的型態,這邊就使用到了泛型 ( Generic Type ) 的概念。

首先在定義 trigger 屬性的那一行,我們多了一個 Type Variable 名為 T
trigger: <T extends EventHandlerEventNames>(
這個 Type VariableTypeScript 知道下面 2 件事:
  1. T 所代表的真正型別在定義的當下是無法明確確認的,因為在定義的當下沒有足夠資訊去辨識 T 倒底指的是什麼型別。
  2. 雖然 vscode 無法指明 T 要表達的型別,但可以確認的是,它一定是 EventHandlerEventNames 的集合或子集合。
    type EventHandlerEventNames = keyof EventHandlerArgs

    EventHandlerEventNames 是 EventHandlerArgs 的所有 key 的集合,也就是等同於
接著將 Type Variable T 分別用於 triggeron 的參數:
trigger: <T extends EventHandlerEventNames>(
event: T,
args: EventHandlerArgs[T]
) => void

on: <T extends EventHandlerEventNames>(
event: T,
callback: (args: EventHandlerArgs[T]) => void
) => void
這麼一來,eventargs 間就存在推論相依關系了,當 event 的型別為 T 時,則 triggerargs 就為 EventHandlerArgs[T],這代表什麼意思呢? 代表當 TypeScript 可以推論出 T'ready' 時,則 args 的型態必為 EventHandlerArgs['ready'],也就是 ReadyEventArgs

on 函式同理可證。

接著我們可以看一下 index.ts:
可以看到 vscode 報錯了,因為 event 的型別被 TypeScript 推論為 'ready' | 'start' | 'stop' 的聯集( Union Type ),所以 click 是不合法的型別。

接著我們將 click 改成 ready:根據上圖,我們只把 click 改成 ready,但我們並沒有修改第二個參數,現在 vscode 有足夠的資訊推論出 trigger 的第二個參數型別是 ReadyEventArgs ,因此報錯。這邊新增了一行 trigger 的呼叫,這次我們 trigger start event,因為 vscode 推論出 start event 必需要傳遞的物件型態為 StartEventArgs 而不是 ReadyEventArgs ,所以報錯,我們在範例一面臨的問題解決了。

on 函式也能正確推論出來。









範例三

假設我們現在遇到一個問題,如果有像類似 on 函式同樣功能的函式,例如 once ,他們之間的 TypeScript Definition 能否共用?

第二個問題 ,trigger 時傳遞的物件與註冊在 on 下的 callback,其參數的 shape 有些不同,例如 trigger 時所傳遞的參數,會成為 on callback 下參數的其中一個屬性,名為 data,那這該如何是好?

幸好 TypeScript 提供了一個強大的設計名為 Mapped Types,它可以幫我們解決很多這樣的問題

首先我們新增 once 的定義並實作之
interface.ts:
type IndexableObject = Record<string, unknown>

interface ReadyEventArgs {
readyTime: number
}

interface StartEventArgs {
jsonData: string
}

interface StopEventArgs {
result: IndexableObject
}

interface EventHandlerArgs {
ready: ReadyEventArgs
start: StartEventArgs
stop: StopEventArgs
}

type EventHandlerEventNames = keyof EventHandlerArgs

export interface EventHandler {
trigger: <T extends EventHandlerEventNames>(
event: T,
args: EventHandlerArgs[T]
) => void
on: <T extends EventHandlerEventNames>(
event: T,
callback: (args: EventHandlerArgs[T]) => void
) => void
once: <T extends EventHandlerEventNames>(
event: T,
callback: (args: EventHandlerArgs[T]) => void
) => void
}

index.ts
import { EventEmitter } from 'events'
import { EventHandler } from './interface'

function getMyEventEmitter(): EventHandler {
const emitter = new EventEmitter()
const myEventEmitter: EventHandler = {
trigger(event, args) {
emitter.emit(event, args)
},
on(event, callback) {
emitter.on(event, callback)
},
once(event, callback) {
emitter.once(event, callback)
},
}

return myEventEmitter
}

const myEmitter = getMyEventEmitter()

myEmitter.trigger('ready', { readyTime: 1 })
myEmitter.trigger('start', { jsonData: '' })

myEmitter.on('ready', (args) => {
console.log(args.readyTime)
})


interface.ts 裡,ononcecallback 屬性型別是相同的,那麼是否可以事先定義一種型別可以套用到所有相同的 event callback 上? 答案如下:
type EventHandlerEventNames = keyof EventHandlerArgs

type EventHandlerOnCallbackSignature = {
[K in EventHandlerEventNames]: (args: EventHandlerArgs[K]) => void
}

export interface EventHandler {
trigger: <T extends EventHandlerEventNames>(
event: T,
args: EventHandlerArgs[T]
) => void
on: <T extends EventHandlerEventNames>(
event: T,
callback: EventHandlerOnCallbackSignature[T]
) => void
once: <T extends EventHandlerEventNames>(
event: T,
callback: EventHandlerOnCallbackSignature[T]
) => void
}

上面的程式多定義了一個新 Type EventHandlerOnCallbackSignature。它的定義就是一個典型的 Mapped Types
type EventHandlerOnCallbackSignature = {
[K in EventHandlerEventNames]: (args: EventHandlerArgs[K]) => void
}
  1. [K in EventHandlerEventNames],中的 K 是一 Type Variable
  2. K in Something,其中的 Something 是一連串型別的集合,在這裡是 EventHandlerEventNames 。
  3. EventHandlerEventNames = keyof  EventHandlerArgs = 'ready' | 'start' | 'stop'
  4. [K in Something] 代表著 TypeScript 會迭代 Something 裡頭的每一個個別的 Type,並建立一個新的 Mapped Type
我們可以在 vscode 上,將游標移動到 EventHandlerOnCallbackSignature 上,vscode 會顯示推論出來的結果。如同上圖,EventHandlerOnCallbackSignatureType Defintion 就相當於分別定義了三個屬性 ready, start and stop,都是一 function type,其中的 args 分別對應到 ReadyEventArgs, StartEventArgs, StopEventArgs

我們可以更進一步的重覆利用 on 的型別定義,套用到 once 上:
export interface EventHandler {
trigger: <T extends EventHandlerEventNames>(
event: T,
args: EventHandlerArgs[T]
) => void
on: <T extends EventHandlerEventNames>(
event: T,
callback: EventHandlerOnCallbackSignature[T]
) => void
once: EventHandler['on']
}

回到第二個問題,現在只要修改 EventHandlerOnCallbackSignature 就可以解決,例如:
interface EventHandlerOnCallbackArgs<T extends EventHandlerEventNames> {
data: EventHandlerArgs[T]
id: number
}

type EventHandlerOnCallbackSignature = {
[K in EventHandlerEventNames]: (args: EventHandlerOnCallbackArgs<K>) => void
}
  1. EventHandlerOnCallbackArgs 包含一個 dataid 屬性。
  2. EventHandlerOnCallbackArgs['data'] 屬性在定義時,vscode 因沒有足夠的資訊,所以無法推論。只有在得知 T 所指的型別為何後,它才能推論出該 data 屬性型別。
  3. TEventHandlerEventNames = keyof EventHandlerArgs = 'ready' | 'start' | 'stop',意思就是 T 只能是 'ready' ,'start' or 'stop' 這三者型別的其中之一,其它皆不合法。
  4. 給定 T'stop',則 data 型別就可以推論出是 EventHandlerArgs['stop'] ,也就是 StopEventArgs
上面的定義表明,當呼叫 trigger 時如果是
myEmitter.trigger('ready', { readyTime: 1 })
而預期 on 函式的 callbackargs 就會是像底下的常數 p (id  的值不是重點)
const p: EventHandlerOnCallbackArgs<'ready'> = {
data: {
readyTime: 1
},
id: 1
}

然而這只是修改 TypeScript Defintion ,如果回到 index.ts 看一下我們實際上的實作,就會發現一個大問題:
function getMyEventEmitter(): EventHandler {
const emitter = new EventEmitter()
const myEventEmitter: EventHandler = {
trigger(event, args) {
emitter.emit(event, args)
},
on(event, callback) {
emitter.on(event, callback)
},
once(event, callback) {
emitter.once(event, callback)
},
}

return myEventEmitter
}
EventHandleron 接受的 callback 其型別是 
EventHandlerOnCallbackSignature<'start' | 'stop' | 'ready'>
callback 的參數型別為 
EventHandlerOnCallbackArgs<'start' | 'stop' | 'ready'>

但是,當 trigger 函式呼叫時,實際上帶入的參數型別是 
EventHandlerArgs<'start' | 'stop' | 'ready'>
這與 on callback 的參數型別不符合,而且 TypeScript 也沒有發現,原因是 EventEmitter 的型別定義,關於函式參數型別皆是 any:
這符合常理,因為當我們在使用 EventEmitter 時,NodeJS 在定義上不可能知道,這些 trigger/on 會傳遞什麼樣的參數,因此預設為 any。但我們現在外面多封裝了一層,並賦與更嚴謹的型別定義,當我們將定義較為嚴謹的 callback 傳給定義較為寬鬆的 EventEmitter.on 時,vscode 只會檢查該 callback 是否相容於 EventEmitter.on 的定義,無從得知 on callback 的參數型別已經與 trigger 時的參數型別不同了。

範例四

針對以上範例三面臨實作與定義上不一致的問題,我們可以透過 Module augmentation ,使我們的程式在利用 EventEmitter 時,能透過更嚴謹的 TypeScript 定義, 使 vscode 限制 EventEmitter.on 會收到的參數型別為何。例如:
import 'events'

// skip intermediate definitions

type EventHandlerEventNames = keyof EventHandlerArgs

type EventEmitterlListenerSignature = (
args: EventHandlerArgs[EventHandlerEventNames]
) => void

declare module 'events' {
interface EventEmitter {
on: (
event: string,
listener: EventEmitterlListenerSignature
) => void
}
}
上面的程式碼中,首先一開始 import 'events',因為我們需要覆寫預設的 EventEmitter interface 的定義,而該定義是 import 'events' 獲得的。

接著下面的定義式: 
declare module 'events' {
interface EventEmitter {
on: (
event: string,
listener: EventEmitterlListenerSignature
) => void
}
}
重新改寫了 EventEmitter.on 的定義,原本的 listener(...args: any[]) => void 型別,現在改變為 EventEmitterlListenerSignature,而 EventEmitterlListenerSignature 的定義:
type EventEmitterlListenerSignature = (
args: EventHandlerArgs[EventHandlerEventNames]
) => void

  1. 是一個 function type 。
  2. function type 接受一個參數,其型態為 EventHandlerArgs[EventHandlerEventNames]
  3. EventHandlerEventNames = 'ready' | 'start' | 'stop' 。  
所以:
EventHandlerArgs[EventHandlerEventNames] =
EventHandlerArgs['ready' | 'start' | 'stop'] =
ReadyEventArgs | StartEventArgs | StopEventArgs

所以:
type EventEmitterlListenerSignature = ( args: EventHandlerArgs[EventHandlerEventNames] ) => void
等同於:
type EventEmitterlListenerSignature = ( args: ReadyEventArgs | StartEventArgs | StopEventArgs ) => void

我們當然也可以直接寫成上面第二定義式,但未來如果有新的 Event 要定義時,我們得每一行都要檢查與修改。
用第一式的好處就在於,我們只要對 EventHandlerArgs 做更新,其它的定義推論出來的型別會自動更新。

因此,現在的 EventEmitter.on 已經被定義成一個函式,第一個參數是 string type,第二個參數為一 function type,該 function 接受一個參數,其型別為 EventHandlerArgs 所有屬性型別的集合,依目前的定義就是 ReadyEventArgs | StartEventArgs | StopEventArgs

我們會定義 EventEmitter.on 是接收  ReadyEventArgs | StartEventArgs | StopEventArgs 合理,因為呼叫 EventEmitter.trigger 時帶得就是這些參數型別。 

當我們再次回到 index.ts 時,就發現 TypeScript 抱怨有錯誤了:
  1. EventEmitter.on 的型別為 EventEmitterlListenerSignature
  2. EventHandler.on 的型別為 EventHandlerOnCallbackSignature 。
  3. EventEmitterlListenerSignature 與 EventHandlerOnCallbackSignature 不相容。
  4. EventHandler.on 預期接受的 args 是 EventHandlerOnCallbackArgs
  5. EventHandlerOnCallbackArgs = { data: ReadyEventArgs; id: number } | { data: StartEventArgs; id: number } | { data: StopEventArgs; id: number }  
  6. EventEmitter.on callback args 的型別為 EventHandlerArgs 
  7. EventHandlerArgs = ReadyEventArgs | StartEventArgs | StopEventArgs
  8. 所以 { data: ReadyEventArgs; id: number } | { data: StartEventArgs; id: number } | { data: StopEventArgs; id: number } 不是 ReadyEventArgs | StartEventArgs | StopEventArgs 的集合的一部分。
這樣的錯誤正是我們需要的,我們就一定得修改我們的實作 ,好使這個錯誤消失,這樣我們的實作與定義就會再次一致。

為了將 args 型別為 EventHandlerArgs 變成 EventHandler.on 可以接受的 EventHandlerOnCallbackArgs,底下的實作改成註冊一個 anonymous callback,在裡頭將 args 轉成 EventHandler.on 可以接受的型別
import { EventEmitter } from 'events'
import {
EventHandler,
EventHandlerOnCallbackArgs,
EventHandlerEventNames,
} from './interface'

function getMyEventEmitter(): EventHandler {
const emitter = new EventEmitter()
const myEventEmitter: EventHandler = {
trigger(event, args) {
emitter.emit(event, args)
},
on(event, callback) {
emitter.on(event, function(args) {
const newArgs: EventHandlerOnCallbackArgs<EventHandlerEventNames> = {
data: args,
id: 1,
}
callback(newArgs)
})
},
once(event, callback) {
emitter.once(event, callback)
},
}

return myEventEmitter
}

然而這樣寫卻還是會有問題,vscode 仍會抱怨 callback 接受的參數 newArgs 仍然不相同 callback 的參數型別:上面 vscode 的錯誤看起來嚇死人,我們接著來一行一行地看:
首先是這一句:

Argument of type 'EventHandlerOnCallbackArgs<"ready" | "start" | "stop">' is not assignable to parameter of type 'EventHandlerOnCallbackArgs<"ready"> & EventHandlerOnCallbackArgs<"start"> & EventHandlerOnCallbackArgs<"stop">'.

當型別不相容時,TypeScript 首先會丟出 Argument of type A is not assignable to type B,例如:
interface Test {
a1: number
}

const test: Test = {
a1: '3',
}
vscodea1 下標處會出現紅蚯蚓,並抱怨:Type 'string' is not assignable to type 'nubmer' 這句話就是說 '3' 要賦值給 a1 這個屬性,但 a1 的型別是 number,而 '3' 的型別是 string ,所以 string is not assignable to number

同理 EventHandlerOnCallbackArgs<"ready" | "start" | "stop">  型別也無法相容於 EventHandlerOnCallbackArgs<"ready"> & EventHandlerOnCallbackArgs<"start"> & EventHandlerOnCallbackArgs<"stop"> ,意思就是 newArgs 的型別 EventHandlerOnCallbackArgs<'ready' | 'start' | 'stop'> callback 的參數型別 EventHandlerOnCallbackArgs<"ready"> & EventHandlerOnCallbackArgs<"start"> & EventHandlerOnCallbackArgs<"stop"> 之間是無法相容的。

EventHandlerOnCallbackArgs<'ready' | 'start' | 'stop'> 這個型別是 由 EventHandlerOnCallbackArgs<EventHandlerEventNames> 推論出來的,因為 EventEmitter.onevent 會是 EventHandlerEventNames type ,所以這裡 newArgs 指定的型別為 EventHandlerOnCallbackArgs<EventHandlerEventNames> 合理。

又:
EventHandlerOnCallbackArgs<EventHandlerEventNames> =
EventHandlerOnCallbackArgs<'ready' | 'start' | 'stop'> =
EventHandlerOnCallbackArgs<'ready'> | EventHandlerOnCallbackArgs<'start'> | EventHandlerOnCallbackArgs<'stop'>

各位看到這裡,不曉得有沒有發現 newArgs 的型別為 
EventHandlerOnCallbackArgs<'ready'> | EventHandlerOnCallbackArgs<'start'> | EventHandlerOnCallbackArgs<'stop'>
但在呼叫 callback 時, TypeScript 推論 callback 的參數型別卻是 
EventHandlerOnCallbackArgs<'ready'> &EventHandlerOnCallbackArgs<'start'> & EventHandlerOnCallbackArgs<'stop'>
前一個是 Union Type,而後者是 Intersection Type

這其實是因為在呼叫 callback 這一行時,該 callback 是此型別: (args: EventHandlerOnCallbackArgs<'ready'> ) => void | (args: EventHandlerOnCallbackArgs<'start'> ) => void | (args: EventHandlerOnCallbackArgs<'stop'> ) => void
會推論出這樣的型別也不難理解,callback 的確會是 (args: EventHandlerOnCallbackArgs<'ready'> ) => void OR
(args: EventHandlerOnCallbackArgs<'start'> ) => void  OR
(args: EventHandlerOnCallbackArgs<'stop'> ) => void
這三者其中之一,而要能滿足這三種型別的 args 的參數型別必然就是

EventHandlerOnCallbackArgs<'ready'> & EventHandlerOnCallbackArgs<'start'> & EventHandlerOnCallbackArgs<'stop'>

也就是只有以下的的值才能滿足這個型別:
type P = EventHandlerOnCallbackArgs<'ready'> &
EventHandlerOnCallbackArgs<'start'> &
EventHandlerOnCallbackArgs<'stop'>

const pp: P = {
data: {
jsonData: '', // StartEventArgs's property
readyTime: 1, // ReadyEventArgs's property
result: {}, // StopEventArgs's property
},
id: 1,
}
EventEmitter.on 傳進來的參數只會是 
EventHandlerOnCallbackArgs<'ready'> | EventHandlerOnCallbackArgs<'start'> | EventHandlerOnCallbackArgs<'stop'>

目前想到的作法就是強制 callback 轉型成另一種型別, 令該 callbackargs 型別等同於 EventEmitter.on 的參數型別。
// interface.ts
export type AllEventHandlerOnCallbackArgs =
EventHandlerOnCallbackArgs<EventHandlerEventNames>

export type EventHandlerCallbackListener = (
args: AllEventHandlerOnCallbackArgs
) => void


// index.ts
import { EventEmitter } from 'events'
import {
EventHandler,
AllEventHandlerOnCallbackArgs,
EventHandlerCallbackListener,
} from './interface'

function getMyEventEmitter(): EventHandler {
const emitter = new EventEmitter()
const myEventEmitter: EventHandler = {
trigger(event, args) {
emitter.emit(event, args)
},
on(event, callback) {
emitter.on(event, function(args) {
const newArgs: AllEventHandlerOnCallbackArgs = {
data: args,
id: 1,
};
(callback as EventHandlerCallbackListener)(newArgs)
})
},
once(event, callback) {
emitter.once(event, callback)
},
}

return myEventEmitter
}

完整版答案
interface.ts
import 'events'

type IndexableObject = Record<string, unknown>

interface ReadyEventArgs {
readyTime: number
}

interface StartEventArgs {
jsonData: string
}

interface StopEventArgs {
result: IndexableObject
}

interface EventHandlerArgs {
ready: ReadyEventArgs
start: StartEventArgs
stop: StopEventArgs
}

export type EventHandlerEventNames = keyof EventHandlerArgs

export interface EventHandlerOnCallbackArgs<T extends EventHandlerEventNames> {
data: EventHandlerArgs[T]
id: number
}

type EventHandlerOnCallbackSignature = {
[K in EventHandlerEventNames]: (args: EventHandlerOnCallbackArgs<K>) => void
}

type EventEmitterlListenerSignature = (
args: EventHandlerArgs[EventHandlerEventNames]
) => void

export type AllEventHandlerOnCallbackArgs =
EventHandlerOnCallbackArgs<EventHandlerEventNames>

export type EventHandlerCallbackListener = (
args: AllEventHandlerOnCallbackArgs
) => void

export interface EventHandler {
trigger: <T extends EventHandlerEventNames>(
event: T,
args: EventHandlerArgs[T]
) => void
on: <T extends EventHandlerEventNames>(
event: T,
callback: EventHandlerOnCallbackSignature[T]
) => void
once: EventHandler['on']
}

declare module 'events' {
interface EventEmitter {
on: (
event: string,
listener: EventEmitterlListenerSignature
) => void
}
}


index.ts
import { EventEmitter } from 'events'
import {
EventHandler,
AllEventHandlerOnCallbackArgs,
EventHandlerCallbackListener,
} from './interface'

function getMyEventEmitter(): EventHandler {
const emitter = new EventEmitter()
const myEventEmitter: EventHandler = {
trigger(event, args) {
emitter.emit(event, args)
},
on(event, callback) {
emitter.on(event, function(args) {
const newArgs: AllEventHandlerOnCallbackArgs = {
data: args,
id: 1,
};
(callback as EventHandlerCallbackListener)(newArgs)
})
},
once(event, callback) {
emitter.once(event, callback)
},
}

return myEventEmitter
}

const myEmitter = getMyEventEmitter()

myEmitter.trigger('ready', { readyTime: 1 })
myEmitter.trigger('start', { jsonData: '' })
myEmitter.trigger('stop', { result: {} })

myEmitter.on('stop', (args) => {
console.log(args.data.result)
})





留言