Consider the following overloaded function:
function foo(arg1: string, cb: (err: Error|null, res: string) => void): void
function foo(arg1: string, arg2:
We can use the 3.0 feature of Tuples in rest parameters and spread expressions to get a union of the overload parameters but we need to add a case for each number of overloads the function has:
export type GetOverloadArgs<T> =
T extends { (...o: infer U) : void, (...o: infer U2) : void, (...o: infer U3) : void } ? U | U2 | U3:
T extends { (...o: infer U) : void, (...o: infer U2) : void } ? U | U2 :
T extends { (...o: infer U) : void } ? U : never
So for example for foo
type fooParams = GetOverloadArgs<typeof foo>
// will be
type fooParams = [string, (err: Error | null, res: string) => void] | [string, string, (err: Error | null, res: string) => void]
From this we can use a type similar to your Promisify
to create a function for each parameter set in the union :
export type PromisifyOne<T extends any[]> =
T extends [Callback<infer U>?] ? () => Promise<U> :
T extends [infer T1, Callback<infer P>] ? (arg1: T1) => Promise<P> :
T extends [infer T1, infer T2, Callback<infer U>?] ? (arg1: T1, arg2: T2) => Promise<U> :
T extends [infer T1, infer T2, infer T3, Callback<infer U>?]? (arg1: T1, arg2: T2, arg3: T3) => Promise<U> :
T extends [infer T1, infer T2, infer T3, infer T4, Callback<infer U>?] ? (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<U> :
T;
And using the distributive behavior of conditional types we can create a union of all the overloads:
export type Promisify<T> =PromisifyOne<GetOverloadArgs<T>>
export type Promisify<T> =PromisifyOne<GetOverloadArgs<T>>
type fooOverloadUnion = Promisify<typeof foo>
// Same as
type fooOverloadUnion = ((arg1: string) => Promise<string>) | ((arg1: string, arg2: string) => Promise<string>)
To make this callable again we ca use transform the union to an intersection, using UnionToIntersection, with the final result being:
export type Callback<T> = (err: Error | null, reply: T) => void;
export type PromisifyOne<T extends any[]> =
T extends [Callback<infer U>?] ? () => Promise<U> :
T extends [infer T1, Callback<infer P>?] ? (arg1: T1) => Promise<P> :
T extends [infer T1, infer T2, Callback<infer U>?] ? (arg1: T1, arg2: T2) => Promise<U> :
T extends [infer T1, infer T2, infer T3, Callback<infer U>?]? (arg1: T1, arg2: T2, arg3: T3) => Promise<U> :
T extends [infer T1, infer T2, infer T3, infer T4, Callback<infer U>?] ? (arg1: T1, arg2: T2, arg3: T3, arg4: T4) => Promise<U> :
T;
export type GetOverloadArgs<T> =
T extends { (...o: infer U) : void, (...o: infer U2) : void, (...o: infer U3) : void } ? U | U2 | U3:
T extends { (...o: infer U) : void, (...o: infer U2) : void } ? U | U2 :
T extends { (...o: infer U) : void } ? U : never
type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
export type Promisify<T> = UnionToIntersection<PromisifyOne<GetOverloadArgs<T>>>
// Sample
declare function foo(arg1: string, cb: (err: Error|null, res: string) => void): void
declare function foo(arg1: string, arg2: string, cb: (err: Error|null, res: string) => void): void
declare const fooPromise: Promisify<typeof foo>
let r = fooPromise("")
let r2 = fooPromise("", "")