Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In TypeScript, how do I assert the value of a class variable using a method?

Tags:

typescript

I want a method that will tell typescript my client was initialised (is no longer null). Basically, I'm able to do this but in a way that seems needlessly verbose. Here's my current implementation:

export abstract class CacheService {
    storeClient: ICacheStoreInterface | null = null

    protected constructor(storeClientGetter: () => Promise<ICacheStoreInterface>) {
        this.logger = Container.get(Logger)
        void this.initialise(storeClientGetter)
    }

    private checkInitialisation(client: ICacheStoreInterface | null): asserts client is ICacheStoreInterface {
        if (!this.storeClient) {
            throw new Error('Attempting to access cache before initialisation')
        }
    }

    private async initialise(storeClientGetter: () => Promise<ICacheStoreInterface>) {
        try {
            this.storeClient = await storeClientGetter()
        } catch (error) {
            this.logger.error(`Error initialising cache service:\n${error}`)
        }
    }

    public async set(key: storeKey, value: any) {
        this.checkInitialisation(this.storeClient)
        await this.storeClient.set(key, value)
    }

    public async get(key: storeKey) {
        this.checkInitialisation(this.storeClient)

        return this.storeClient.get(key)
    }
}

export interface ICacheStoreInterface {
    get(key: storeKey): Promise<any>
    set(key: storeKey, value: any): Promise<void>
}

export type storeKey = number | string | symbol

TS playground link

What I want to do is achieve the same result BUT without having to explicitly pass this.storeClient to the checkInitialisation method. It seems like it might be possible as both the method and the parent function have access to the variable so maybe they can share type data somehow? In essence, I'm looking for something like asserts this.storeClient is ICacheStoreInterface though that exact example won't work. Is this possible or am I going to have to live with the minor inconvenience of this "pointless" variable?

like image 622
Supperhero Avatar asked May 16 '26 09:05

Supperhero


1 Answers

There's no direct support for type predicates of the form arg.prop is Type, nor for assertion predicates of the form asserts arg.prop is Type. See microsoft/TypeScript#11117 for an open feature request for such functionality, at least for type predicates. It's listed as "awaiting more feedback" so you might want to go there, give it a 👍, and describe your use case (I suppose assertion predicates might be slightly off-topic there, though; not sure)... but for now it's not part of the language.

While you can't write asserts this.storeClient is ICacheStoreInterface, you can write asserts this is {storeClient: ICacheStoreInterface}, which is similar. Or possibly asserts this is this & {storeClient: ICacheStoreInterface}, depending on whether or not the compiler requires the predicate be a strict narrowing (it doesn't seem to, but maybe there are cases where it matters). For your example, anyway, it seems to behave as desired:

private checkInitialization(): asserts this is { storeClient: ICacheStoreInterface } {
    if (!this.storeClient) {
        throw new Error('Attempting to access cache before initialisation')
    }
}

public async get(key: storeKey) {
    this.checkInitialization()
    // this is now this & { storeClient: ICacheStoreInterface; }
    return this.storeClient.get(key) // okay
}

Such property type guards only work when the property you are narrowing is not private; if the property is private then this gets narrowed to never because an object with a private property is not assignable to an object with a public property of the same key. So that's a limitation of this approach.

Playground link to code

like image 101
jcalz Avatar answered May 18 '26 22:05

jcalz



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!