Suppose I'm writing a class with a method that needs to asynchronously mutate arbitrary properties by their name. The method accepts the name of the property (as a string), as well as an async function that calculates a value to assign to that property.
Provided I'm only passing in keys for the public properties, I can just constrain the property name argument with keyof this
and the function argument with an indexed access type on that key. However, that fails for private properties, since keyof this
only includes the public ones.
For instance, in this example the second call to initProperty
is rejected by the type checker:
class Thing {
foo: number = 0;
private bar: number = 0;
// ...
private async init(): Promise<void> {
// Fire off some parallel async requests to initialize our properties
await Promise.all([
this.initProperty('foo', someAsyncFn), // Works because foo is public
this.initProperty('bar', someAsyncFn), // Error because bar is private
// ...
]);
// Do some other stuff...
}
// Initializes this[key] with the resolved value of the Promise returned by fn
private async initProperty<K extends keyof this>(key: K, fn: () => Promise<this[K]>) {
return fn().then(result => this[key] = result);
}
// ...
}
While this example is a bit contrived, I'm mostly interested in whether it's even possible to constrain a method so that it accepts keys for both public and private/protected members of a class, without having to manually enumerate all the acceptable keys in a separate union/interface. In other words, how can I make the method accept all strings S
for which Thing[S]
is a valid type, but still maintain type safety when mutating the corresponding property values?