TypeScript映射类型结合重载函数:基于接口生成保留全部重载的Promise化命令类型
Great question—this is a common pain point when working with callback-heavy APIs in TypeScript, especially when dealing with interfaces that have dozens of overloaded methods. Let’s break down how to solve this without manually rewriting every overload.
First, let’s recap the core requirements:
- Convert every overload of each command to remove the optional callback parameter
- Replace the original return type with a
Promisethat resolves to the callback’s result value - Preserve all overloads (not just the last one, which is what naive mapping types do)
Step 1: Define Base Types (For Context)
Let’s start with the types you referenced to ground our solution:
type Callback<T> = (err: Error | null, result: T) => void; type R = void; // Original return type of callback-based functions // Example interface with overloaded commands interface Commands { set(key: string, value: string, cb?: Callback<'OK'>): R; set(key: string, value: string, flag: string, cb?: Callback<'OK'>): R; set(key: string, value: string, mode: string, duration: number, cb?: Callback<'OK' | undefined>): R; set(key: string, value: string, mode: string, duration: number, flag: string, cb?: Callback<'OK' | undefined>): R; set(key: string, value: string, flag: string, mode: string, duration: number, cb?: Callback<'OK' | undefined>): R; SET(key: string, value: string, cb?: Callback<'OK'>): R; SET(key: string, value: string, flag: string, cb?: Callback<'OK'>): R; SET(key: string, value: string, mode: string, duration: number, cb?: Callback<'OK' | undefined>): R; SET(key: string, value: string, mode: string, duration: number, flag: string, cb?: Callback<'OK' | undefined>): R; SET(key: string, value: string, flag: string, mode: string, duration: number, cb?: Callback<'OK' | undefined>): R; }
Step 2: Build Helper Type Utilities
We’ll create reusable type tools to handle each part of the transformation:
Extract the Callback’s Result Type
First, pull out the value that the callback resolves to:
type ExtractCallbackResult<C> = C extends Callback<infer T> ? T : never;
Remove the Callback Parameter from Args
Next, strip the optional callback from the end of the parameter list (only if it’s actually a callback):
type RemoveCallbackParam<T extends any[]> = T extends [...infer Rest, (infer C)?] ? C extends Callback<any> ? Rest : T : T;
Promisify a Single Function Signature
Convert a single non-overloaded function to its Promise-based equivalent:
type PromisifySingleFunction<F> = F extends (...args: infer Args) => infer _ ? Args extends [...infer Rest, (infer C)?] ? C extends Callback<infer Res> ? (...args: RemoveCallbackParam<Args>) => Promise<Res> : never : never : never;
Handle Overloaded Functions
This is the critical part—TypeScript lets us match overloaded function signatures using conditional types. We’ll recursively extract each overload, promisify it, and merge them with intersection types:
type PromisifyOverloadedFunction<F> = // Handle up to 5 overloads (adjust if you need more) F extends { (...args: infer Args1): infer R1; (...args: infer Args2): infer R2; (...args: infer Args3): infer R3; (...args: infer Args4): infer R4; (...args: infer Args5): infer R5 } ? PromisifySingleFunction<(...args: Args1) => R1> & PromisifySingleFunction<(...args: Args2) => R2> & PromisifySingleFunction<(...args: Args3) => R3> & PromisifySingleFunction<(...args: Args4) => R4> & PromisifySingleFunction<(...args: Args5) => R5> : F extends { (...args: infer Args1): infer R1; (...args: infer Args2): infer R2; (...args: infer Args3): infer R3; (...args: infer Args4): infer R4 } ? PromisifySingleFunction<(...args: Args1) => R1> & PromisifySingleFunction<(...args: Args2) => R2> & PromisifySingleFunction<(...args: Args3) => R3> & PromisifySingleFunction<(...args: Args4) => R4> : F extends { (...args: infer Args1): infer R1; (...args: infer Args2): infer R2; (...args: infer Args3): infer R3 } ? PromisifySingleFunction<(...args: Args1) => R1> & PromisifySingleFunction<(...args: Args2) => R2> & PromisifySingleFunction<(...args: Args3) => R3> : F extends { (...args: infer Args1): infer R1; (...args: infer Args2): infer R2 } ? PromisifySingleFunction<(...args: Args1) => R1> & PromisifySingleFunction<(...args: Args2) => R2> : F extends (...args: infer Args) => infer _ ? PromisifySingleFunction<F> : never;
Step 3: Map the Entire Commands Interface
Finally, apply our overload-safe promisifier to every method in the Commands interface:
type PromisedCommands = { [K in keyof Commands]: PromisifyOverloadedFunction<Commands[K]>; };
Verify the Result
Now all overloads are preserved, callback parameters are removed, and return types are correctly converted to Promises:
// Test the set command's overloads const testSet: PromisedCommands['set'] = async (key, value) => 'OK'; // Valid (1st overload) const testSet2: PromisedCommands['set'] = async (key, value, flag) => 'OK'; // Valid (2nd overload) const testSet3: PromisedCommands['set'] = async (key, value, mode, duration) => 'OK'; // Valid (3rd overload) // ...and so on for all 5 overloads // Test the SET command's overloads work the same way const testSET: PromisedCommands['SET'] = async (key, value) => 'OK';
Key Notes
- Adjust overload count: If your commands have more than 5 overloads, extend the
PromisifyOverloadedFunctiontype to match. - Type safety: This solution strictly only removes callback parameters (ignores non-callback trailing params) and correctly infers the Promise’s resolved type from the callback.
- Scalability: Works for 100+ commands without manual maintenance—just update the original
Commandsinterface, andPromisedCommandswill auto-update.
内容的提问来源于stack exchange,提问作者CordlessWool

