tvsbrent
tvsbrent

Reputation: 2599

Typescript: No error for overloaded methods with different return types

Hopefully that title sufficiently explains the situation I'm trying to handle! Recently, I was trying to create an interface for a method that could take either a callback parameter or return a promise. This was to handle not introducing a Promise-based pattern into a legacy part of the codebase that only used callbacks.

Here's an example of the interface I defined and the implementation of that interface:

interface ITest {
    get(callback: () => void): void;

    get(): Promise<void>;
}


class Test implements ITest {
    public get(callback?: () => void): Promise<void> {
        if (typeof callback === 'function') {
            callback();
            return;
        }
        return Promise.resolve();
    }
}

This seemed to work, however, there was a situation that wasn't handled:

const t = new Test();
// Good
t.get(() => console.log('callback'));
// Good
t.get().then(() => console.log('then'));
// Bad - Exception
t.get(() => console.log('callback')).then(() => console.log('then'));

My hope had been that in that last example, Typescript would complain that there wasn't a signature for the function that took a callback and returned a promise. If I made the tweak below, though, TypeScript did complain:

const t: ITest = new Test();

With that change, the last example did trigger a TSC error. I can see why it would, as I'm explicitly saying t is implementing ITest. However, I can see that being something I very easily forget to do.

So, the question is: is there a better way to accomplish what I hope to do?

EDIT: Very quickly got an answer, marked below! If you are interested in what the "compiled" class looks like using that answer:

var Test = /** @class */ (function () {
    function Test() {
    }
    Test.prototype.get = function (callback) {
        if (typeof callback === 'function') {
            callback();
            return;
        }
        return Promise.resolve();
    };
    return Test;
}());

In the end, fundamentally I wasn't aware Typescript would allow and remove those additional declarations of the method. I could see needing to put some additional comments in my code to explain to someone unfamiliar - like I was moments ago - why those additional declarations are needed.

Upvotes: 0

Views: 503

Answers (1)

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 249706

You need to specify the two overloads explicitly to get the kind of error you want. The problem is that while the get method in the class does implement the method in the interface (it satisfies it's constraints) when checking the signature of the function in the class is used. You can specify the two overloads explicitly, and the implementation signature (the one which exists currently) will remain hidden from the outside.

interface ITest {
    get(callback: () => void): void;

    get(): Promise<void>;
}


class Test implements ITest {

    public get(callback: () => void): void;
    public get(): Promise<void>;
    public get(callback?: () => void): Promise<void> {
        if (typeof callback === 'function') {
            callback();
            return;
        }
        return Promise.resolve();
    }
}

const t = new Test();
// Good
t.get(() => console.log('callback'));
// Good
t.get().then(() => console.log('then'));
// Compile time error
t.get(() => console.log('callback')).then(() => console.log('then'));

Upvotes: 4

Related Questions