You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 09:29:51