如何让Promise转发重载addListener函数的类型给调用者?
如何将EventEmitter重载的类型转发给Promise封装函数
问题场景
我扩展了EventEmitter类,通过函数重载实现了调用addListener传入特定字符串时,回调函数能自动进行类型推导:
const codes = { code1: 'code1', code2: 'code2', } as const; type CodeType = typeof codes; interface Code1Interface { a: string }; interface Code2Interface { b: boolean }; class MyEmitter { // extends EventEmitter public addListener(code: CodeType['code1'], callback: (event: Code1Interface) => void): void; public addListener(code: CodeType['code2'], callback: (event: Code2Interface) => void): void; public addListener(code: CodeType[keyof CodeType], callback: (event: any) => void): void; // fallback public addListener(code: any, callback: any) { // 调用父类的addListener逻辑 } }
但将addListener封装为返回Promise的getEvent函数时,TypeScript无法推断其返回类型:
function getEvent(code: CodeType[keyof CodeType]) { // 返回类型为Promise<unknown> return new Promise((resolve, reject) => { const emitter = new MyEmitter(); emitter.addListener(code, (event) => resolve(event)); }) } getEvent(codes.code1).then(x => x) // x的类型为unknown
需要实现将函数重载中定义的接口类型转发给getEvent的调用者,或是找到可复用的泛型映射方案。
解决方案
方案1:统一事件类型映射表(推荐)
先定义一个事件类型映射表,将事件code与对应的接口类型绑定,后续MyEmitter和getEvent都能复用这个映射,比函数重载更易维护:
const codes = { code1: 'code1', code2: 'code2', } as const; interface Code1Interface { a: string }; interface Code2Interface { b: boolean }; // 核心:定义事件code到类型的映射表 type EventMap = { [codes.code1]: Code1Interface; [codes.code2]: Code2Interface; }; // 改造MyEmitter,用泛型替代重载 class MyEmitter { public addListener<K extends keyof EventMap>( code: K, callback: (event: EventMap[K]) => void ): void; // 兼容其他未知code的fallback public addListener(code: string, callback: (event: any) => void): void; public addListener(code: any, callback: any) { // 调用父类addListener逻辑 } } // 改造getEvent为泛型函数 function getEvent<K extends keyof EventMap>(code: K): Promise<EventMap[K]> { return new Promise((resolve) => { const emitter = new MyEmitter(); emitter.addListener(code, resolve); }); } // 类型推导正常工作 getEvent(codes.code1).then(x => x.a); // x类型为Code1Interface getEvent(codes.code2).then(x => x.b); // x类型为Code2Interface
方案2:提取原有重载的类型(无需修改MyEmitter)
如果不想改动已有的MyEmitter重载代码,可以通过TypeScript的类型工具提取addListener的重载类型,推导对应事件类型:
// 提取MyEmitter中addListener的所有重载类型 type AddListenerOverloads = MyEmitter['addListener']; // 根据code类型推导对应的事件类型 type EventTypeForCode<C> = C extends Parameters<Extract<AddListenerOverloads, (code: C, callback: any) => void>>[0] ? Parameters<Extract<AddListenerOverloads, (code: C, callback: (e: infer E) => void) => void>>[1] extends (e: infer E) => void ? E : never : never; // 改造getEvent为泛型函数 function getEvent<C extends CodeType[keyof CodeType]>(code: C): Promise<EventTypeForCode<C>> { return new Promise((resolve) => { const emitter = new MyEmitter(); emitter.addListener(code, resolve); }); } // 类型推导正常 getEvent(codes.code1).then(x => x.a); getEvent(codes.code2).then(x => x.b);
方案对比
- 方案1更适合长期维护:新增事件只需修改
EventMap,所有相关代码自动复用类型,扩展性和可读性更强。 - 方案2适合兼容现有代码:无需改动已有的
MyEmitter重载逻辑,但类型推导的代码可读性稍弱。
内容的提问来源于stack exchange,提问作者Jeff
相关产品推荐
相关产品推荐

