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

TypeScript映射类型结合重载函数:基于接口生成保留全部重载的Promise化命令类型

Preserving All Overloads While Promisifying Callback-Based Functions in TypeScript

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 Promise that 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 PromisifyOverloadedFunction type 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 Commands interface, and PromisedCommands will auto-update.

内容的提问来源于stack exchange,提问作者CordlessWool

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 18:02:35