Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can not get return type when util.promisify an function with overloading definations #26048

Closed
huan opened this issue Jul 29, 2018 · 13 comments
Closed
Labels
Question An issue which isn't directly actionable in code

Comments

@huan
Copy link

huan commented Jul 29, 2018

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:

@mattmccutchen
Copy link
Contributor

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.

@huan huan changed the title Can not get return type when util.promisify a overrided function Can not get return type when util.promisify an function with overloading definations Jul 30, 2018
@huan
Copy link
Author

huan commented Jul 30, 2018

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?

@mattmccutchen
Copy link
Contributor

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 grpc/grpc-node#54 to get a better solution.

@huan
Copy link
Author

huan commented Jul 30, 2018

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!

@rrousselGit
Copy link

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.

@rrousselGit
Copy link

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 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]> };

@huan
Copy link
Author

huan commented Aug 2, 2018

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?

@rrousselGit
Copy link

You can do the following:

declare module 'util' {
    function promisify<T>(fn: T): Promisify<T>;
}

@huan
Copy link
Author

huan commented Aug 2, 2018

Thank you so much for your code.

I had just tried and get the following error (with TypeScript@3.1.0-dev.20180802)

[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!

@rrousselGit
Copy link

rrousselGit commented Aug 2, 2018

You need to tell vscode to use typescript 3.X.X instead of 2.9.X

Just click on that :

image

@huan
Copy link
Author

huan commented Aug 2, 2018

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!

@tanduong
Copy link

tanduong commented Jun 15, 2019

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.

@typescript-bot
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

6 participants