TypeScript Version: 3.1.0-dev.201xxxxx
Search Terms:
Code
import util from 'util'
class Test {
public test (s: string, callback: (error: Error | null, r: number) => void): void
public test (s: string, s1: string, callback: (error: Error | null, r: number) => void): void
public test (
...args: any[]
): void {
return
}
}
const t = new Test()
const func = util.promisify(t.test)
// const func: (arg1: string) => Promise<{}>
Expected behavior:
func's type should be const func: (arg1: string) => Promise<number>
Actual behavior:
func's type is const func: (arg1: string) => Promise<{}>
Promise<{}> should be Promise<number>
Playground Link:
Related Issues:
What I believe is going on here: The compiler tries the overloads of promisify in order. When it tries the one-parameter overload, it tries to infer the type arguments from the _last_ call signature of t.test (reference), makes no inference for TResult, defaults it to {}, and finds that t.test is compatible with that overload of promisify even with TResult = {}.
I expect the TypeScript team will consider this behavior a design limitation. There are some workarounds that might get promisify to give you one of the two overloads you want, such as switching the order of the overloads of Test.test. But assuming what you really want is to preserve both overloads, your best option is probably to use the "custom promisify" pattern like this.
Thank you very much for explaining this problem so clearly and in detail.
The custom promisify solution looks great, however, what I want to promisify is a stub lib which is auto-generated TS code from gRPC, so I have no chance to modify that source code to add anycustom promisify code.
So it seems that there's no any workaround for me to get the promisified TResult automatically?
You should be able to write a namespace declaration that will merge with the original function definition. If you can't get it to work, please post your code and I will take a look if I have time. If you like, you could try to help with https://github.com/grpc/grpc-node/issues/54 to get a better solution.
Thanks for sending me the link from grpc-node, it's just what I want.
I understand that we can write a namespace declaration and merge it with the original function definition.
However, if the grpc protocol buffers definition is changed, the namespace declaration have to be changed manually, I feel that's not what I want.
So I decided to use the callback right now, and keep eyes on the async/await grpc-node issue.
Thanks again for your help!
Related to this issue, is there any way to iterate over methods overload ?
With objects we can do the following :
type Foo<T extends {}> = {
[J in keyof T]: MyClass<T[J]>
}
If we could do the same thing with methods overloads we could have a promisify working with overloaded functions too.
I took the liberty to ask a StackOverflow question about my last comment.
It seems like with typescript 3.0 it is now possible thanks to spread tupples.
Titian Cernicova-Dragomir gave a nice example on how to use these to make promisify work with _any_ method overload. Go give him a +1
Anyway here's the thing:
export type Callback<T> = (err: Error | null, reply: T) => void;
// prettier-ignore
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> :
never;
// prettier-ignore
export type GetOverloadArgs<T> =
T extends {
(...o: infer U) : void,
(...o: infer U2) : void,
(...o: infer U3) : void,
(...o: infer U4) : void,
(...o: infer U5) : void,
(...o: infer U6) : void,
(...o: infer U7) : void
} ? U | U2 | U3 | U4 | U5 | U6 | U7:
T extends {
(...o: infer U) : void,
(...o: infer U2) : void,
(...o: infer U3) : void,
(...o: infer U4) : void,
(...o: infer U5) : void,
(...o: infer U6) : void,
} ? U | U2 | U3 | U4 | U5 | U6:
T extends {
(...o: infer U) : void,
(...o: infer U2) : void,
(...o: infer U3) : void,
(...o: infer U4) : void,
(...o: infer U5) : void,
} ? U | U2 | U3 | U4 | U5:
T extends {
(...o: infer U) : void,
(...o: infer U2) : void,
(...o: infer U3) : void,
(...o: infer U4) : void,
} ? U | U2 | U3 | U4 :
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;
// prettier-ignore
type UnionToIntersection<U> = (U extends any ? (k: U)=>void : never) extends ((k: infer I)=>void) ? I : never
which you can then uses with
export type Promisify<T> = UnionToIntersection<
PromisifyOne<GetOverloadArgs<T>>
>;
export type PromisifyAll<T extends {}> = { [J in keyof T]: Promisify<T[J]> };
That's awesome, and it's what I want. Seems TypeScript 3.0 has some cool new features and worth to a upgrade.
I had given him a +1, and what's in my mind right now is:
Could we use some technic to extend the promisify definition comes with the node types under this TypeScript 3.0 new feature?
Because it seems not possible to update the @types/node because it's not compatible with old ts version?
You can do the following:
declare module 'util' {
function promisify<T>(fn: T): Promisify<T>;
}
Thank you so much for your code.
I had just tried and get the following error (with [email protected])
[ts] JSDoc types can only be used inside documentation comments.
type Callback<T> = (err: Error | null, reply: T) => void
[ts] A rest parameter must be of an array type.
(...o: infer U) : void,
Did you get it work in your vscode, and if so, would you like to share a complete defination code so that I could compare it with mine?
Thanks again!
You need to tell vscode to use typescript 3.X.X instead of 2.9.X
Just click on that :

Oops, I had set "typescript.tsdk": "./node_modules/typescript/lib" in the .vscode/settings.json but you are right, I have to set it manually.
After switch to TypeScript 3.0, your code work like a charm!
Good job buddy, really appreciate it!
I found another solution before seeing this thread. In the case of grpc generated typescript, the order of the overloaded function is guaranteed to be consistent and the last overloaded function signature is the one get used for infer. So this is working:
export type Promisify<T> = {
[K in keyof T]: T[K] extends (req: infer U, b: any, c: any, callback: (e: any, r: infer V) => void) => any
? (r: U) => Promise<V>
: never
};
// usage
export type IYourServiceClientStub = Promisify<IYourServiceClient>;
GetOverloadArgs is a clever way to make it works, but for my use cases, I only need to work with 1 overloaded version of the underline function.
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.
Most helpful comment
I took the liberty to ask a StackOverflow question about my last comment.
It seems like with typescript 3.0 it is now possible thanks to spread tupples.
https://stackoverflow.com/questions/51650979/type-infererence-with-overloaded-functions/51654917#51654917
Titian Cernicova-Dragomir gave a nice example on how to use these to make
promisifywork with _any_ method overload. Go give him a +1Anyway here's the thing:
which you can then uses with