halfer
halfer

Reputation: 20467

How to tell TypeScript that I know a method exists?

Consider this code:

export default class OptimizelyFeatureFlags implements FeatureFlags {
    private body: string;

    constructor(
        protected callEndpoint: CacheableEntity,
        protected baseUrl: string,
        protected envName: string,
        protected featureId: string
    ) {}

    /**
     * Calls the Optimizely endpoint to get the info
     */
    public async fetch(): Promise<void> {

        // Determine if this is a CallEndpoint (or child thereof)
        if (this.callEndpoint['hasAuthorisationToken'] != undefined) {
            if (!this.callEndpoint.hasAuthorisationToken()) {
                throw Error(
                    'This endpoint requires an auth token to work'
                );
            }
        }

        this.body = await this.callEndpoint.getResource(this.baseUrl + `/v2/features/${this.featureId}`);
    }

   ...

}

Now CacheableEntity is an interface, which requires only getResource() to match the contract. The method hasAuthorisationToken is not defined within it.

Now, if a class has a hasAuthorisationToken() method, I know it is a CallEndpoint, which will implement CacheableEntity. This is a type test - from my research I understand that TypeScript does not offer run-time type tests, since at run-time it is merely JavaScript.

However, this won't compile. In a Jest test, I get:

  ● Test suite failed to run

    src/service/feature-flags/OptimizelyFeatureFlags.ts:31:36 - error TS2339: Property 'hasAuthorisationToken' does not exist on type 'CacheableEntity'.

    31             if (!this.callEndpoint.hasAuthorisationToken()) {
                                          ~~~~~~~~~~~~~~~~~~~~~

I guess there are several ways around this - maybe I can cast the object from CacheableEntity to CallEndpoint? I want to tell the transpiler that I know what I am doing.

Alternatively I could do something like this:

if (this.callEndpoint['hasAuthorisationToken'] != undefined) {
    const hasAuth = this.callEndpoint['hasAuthorisationToken']();
    ...
}

However, I wonder if that is a bit messy. What is the best way around this?

Upvotes: 1

Views: 1324

Answers (2)

Sandro
Sandro

Reputation: 1061

You can use a user defined type guard: https://www.typescriptlang.org/docs/handbook/advanced-types.html#user-defined-type-guards

Upvotes: 2

user3282374
user3282374

Reputation:

Yeah your first option is the best one. Most readable.

if (this.callEndpoint['hasAuthorisationToken'] != undefined) {
    const callEndpoint = this.callEndpoint as CallEndpoint;
    const hasAuth = callEndpoint.hasAuthorisationToken();
    ...
}

Not sure on the != undefined bit. can you do this.callEndpoint instanceof CallEndpoint instead? not sure how you're instantiating these things, but if you can use instanceof, typescript will accept the call to the method on this.callEndpoint without the cast. Like so:

if (this.callEndpoint instanceof CallEndpoint) {
    const hasAuth = this.callEndpoint.hasAuthorisationToken();
    ...
}

Upvotes: 2

Related Questions