To keep track of an initializing/initialized object I want to create a discriminated union with a boolean. And so I wrote the following code:
interface InitializingThing extends BaseThing {
initialized: false;
value: undefined;
}
interface InitializedThing extends BaseThing {
initialized: true;
value: number;
}
type Thing = InitializingThing | InitializedThing;
const thing: Thing = { initialized: false, value: undefined };
console.log(thing);
getThing().then((value: number) => {
thing.value = value;
thing.initialized = true;
}).then(() => {
if (!thing.initialized) {
return;
}
console.log(15 + thing.value);
});
(see on the Typescript playground )
However this gives the errors
Type 'number' is not assignable to type 'undefined'.(2322)
Type 'true' is not assignable to type 'false'.(2322)
I can see from hovering over the console.log(thing) that the type is InitializingThing instead of Thing! Which seems to be the root of the problem, but I'm not sure why the TS compiler would do this.
Your code in principle works fine. However, in above example, TS actually is a bit "too smart" with its control flow analysis. In following variable assignment
const thing: Thing = { initialized: false, value: undefined };
, the compiler narrows the type of thing to InitializingThing by interpreting its initialiser. thing is also assumed to keep this type, since the variable is readonly/const. That is why an error is triggered for the re-assignments in the then clause.
If you enforce thing to really be of type Thing, given sample will compile again:
const thing: Thing = { initialized: false, value: undefined } as Thing; // note the cast here
// or
let thing: Thing = { initialized: false, value: undefined }; // switch to mutable `let`
Playground
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