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?
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With