TypeScript-Handbook icon indicating copy to clipboard operation
TypeScript-Handbook copied to clipboard

please add typing example for function overloading callback or Promise

Open psnider opened this issue 8 years ago • 7 comments

I cannot figure out how to write types for a common pattern I use. I have an async function, that optionally takes a callback, and:

  • calls the callback if one is provided, returning nothing
  • returns a Promise, if there is no callback

Can you please update the handbook to provide an example of the correct way to do this?

My overloaded type declarations are:

doSomethingAsync<T>(done: (error: Error, result: T) => void): void
doSomethingAsync<T>(): Promise<T>

The function definition is:

// desired return type: Promise<T> | void {
doSomethingAsync<T>(done?: (error: Error, result: T) => void): any {
    if (done) {
        something_via_callback(done)
    } else {
        return something_via_promise()
    }
}

Note that I'm using any, which just avoids the type system. But when I use Promise<T> | void I get errors. I cannot figure out how to type this.

Please see this question I asked on StackOverflow, with a link to code on the TypeScript Playground: http://stackoverflow.com/questions/40138730

Also, it seems that I had this code working previously, but I can no longer find a version that did work.

psnider avatar Oct 29 '16 15:10 psnider

This seems to work right now. Any clue if you meant something else?

DanielRosenwasser avatar Nov 01 '16 06:11 DanielRosenwasser

I think even when you use any for the return type or do not write any return type at all for the function implementation, when using the function the correct return type can still be inferred with a given parameter, so you are not "avoid the type system" here. And technically Promise<T> | void is not the correct return type, because it does not provide much safety. It allows Promise<T> in places only void can be allowed for example.

zhengbli avatar Nov 01 '16 06:11 zhengbli

@DanielRosenwasser

Unfortunately, this still persists for me. I'm on a Mac with OSX 10.11.6, and have been using several recent versions of TypeScript.

I see that the code from your link to the Playground doesn't show the error, but I also noticed that the first two lines of the class are no longer comments in that version. Clicking on the example in the StackOverflow issue still shows the error, and I have a copy of the simplified failing code below.

Steps to Reproduce using the example code I created a file, t.ts, with this content:

/// <reference path="typings/globals/es6-shim/index.d.ts" />

declare abstract class SlimDocumentDatabase<T> {
    create(obj: T): Promise<T>
    create(obj: T, done: (error: Error, result?: T) => void): void
}

class SlimAdaptor<DocumentType> implements SlimDocumentDatabase<DocumentType> {
    create(obj: DocumentType, done?: (error: Error, result?: DocumentType) => void) : void | Promise<DocumentType> {
        if (done) {
            done(undefined, obj);
        } else {
            return Promise.resolve<DocumentType>(obj)
        }
    }
}

Then I built it with tsc --module commonjs t.ts, but the typescript compiler outputs this error:

t.ts(9,7): error TS2420: Class 'SlimAdaptor<DocumentType>' incorrectly implements interface 'SlimDocumentDatabase<DocumentType>'.
  Types of property 'create' are incompatible.
    Type '(obj: DocumentType, done?: (error: Error, result?: DocumentType) => void) => void | Promise<Docum...' is not assignable to type '{ (obj: DocumentType): Promise<DocumentType>; (obj: DocumentType, done: (error: Error, result?: D...'.
      Type 'void | Promise<DocumentType>' is not assignable to type 'Promise<DocumentType>'.
        Type 'void' is not assignable to type 'Promise<DocumentType>'.

I've been unable to decipher this error message.

Steps to Reproduce using one of my projects

Here is one of my projects that exhibits the problem: https://github.com/psnider/in-memory-db I just updated the dependencies, so it now uses TypeScript 2.0.6 (I believe I've previously tested with 2.0.0, 2.0.2, and 2.0.3)

I just cloned in-memory-db into a new directory, ran npm install, confirmed that the typescript compiler is v2.0.6, and built the code using npm run build. It compiles cleanly, because my implementing functions are typed as returning any.

git clone [email protected]:psnider/in-memory-db.git
cd in-memory-db
tsc -v
// shows: Version 2.0.6
which tsc
// shows: node_modules/.bin/tsc
npm run build
// builds cleanly

After I edit line 139 to be:

    create(obj: DataType, done?: ObjectCallback<DataType>): Promise<DataType> | void {

the typescript compiler outputs an error similar to the one above.

For reference, the create function in DocumentDatabase is declared as:

export abstract class DocumentDatabase<DocumentType extends DocumentBase> {
    create(obj: DocumentType): Promise<DocumentType>
    create(obj: DocumentType, done: ObjectCallback<DocumentType>): void
}

and in-memory-db.d.ts extends DocumentDatabase.

psnider avatar Nov 01 '16 16:11 psnider

@zhengbli

I think even when you use any for the return type or do not write any return type at all for the function implementation, when using the function the correct return type can still be inferred with a given parameter, so you are not "avoid the type system" here.

I think you're referring to compilation context for the caller of the function. In that case, I agree, because the declaration file has the correct types.

But I'm referring to the implementation. In which case, using any or no type provides no assistance if I implement the function incorrectly. And I implement functions incorrectly all the time! I find the TypeScript feedback invaluable for when I make implementation mistakes, so I want it as tight as possible.

And technically Promise<T> | void is not the correct return type, because it does not provide much safety.

I don't understand this comment. I believe Promise<T> | void is the correct type, as there are two execution paths, each returning a different type. You say it doesn't provide much safety, but I don't know how to type this any more strictly. Can you please provide an example of how to do this?

psnider avatar Nov 01 '16 16:11 psnider

@psnider you need to include the original overloads because otherwise you lose them in the subclass. When the subclass's signature for create is checked for assignability with each overload in the superclass, it will fail because Promise<T> | void is not assignable to Promise<T> nor void individually.

DanielRosenwasser avatar Nov 01 '16 18:11 DanielRosenwasser

Somehow I missed that I can put declarations within the class! I've been using TypeScript since v0.8, and I don't think I've come across this. Thanks.

I looked for the documentation that suggests that I can add declarations to the class, and finally found it at:

  • https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#83-constructor-declarations
  • https://github.com/Microsoft/TypeScript/blob/master/doc/spec.md#61-function-declarations

I still think it would be helpful to add an example such as this to the documentation.

psnider avatar Nov 01 '16 19:11 psnider

How to write the definition for a Promise function when it rejects an Error?

jcyh0120 avatar Jul 28 '18 03:07 jcyh0120