Object is possibly 'undefined' error when using typed map value in closure
borisovg opened this issue ยท 10 comments
TypeScript Version: 4.0.3
Search Terms: "Object is possibly 'undefined' map"
Code
type Thing = { data: any };
const things: Map<string, Thing> = new Map();
function add_thing (id: string, data: any) {
let thing = things.get(id);
if (typeof thing === 'undefined') {
thing = { data };
things.set(id, thing);
}
return () => thing.data;
}
Expected behavior:
No errors - it is not possible for "thing" to be undefined based on the above code.
Actual behavior:
Object is possibly 'undefined' error.
If things
is instead defined as const things = new Map();
then there is no error.
If it is assigned to another constant like this const thing2 = thing;
and that is used in the closure then there is also no error.
Related Issues: #7719
Duplicate of #9998.
Quick workaround: Store it in a new const variable:
const cthing = thing;
return () => cthing.data;
@MartinJohns thanks I am aware of this workaround - edited the description.
It's still a duplicate of #9998. :-)
If
things
is instead defined asconst things = new Map();
then there is no error.
If you declare it like this, then the type is inferred to be Map<any, any>
. As a result the type of thing
is any
, and you opted out of all type-safety checks. That's why you don't get an error in this case.
Another nasty workaround:
const thing = things.get(id) ?? (things.set(id, { data }), things.get(id)!);
return () => thing.data;
(Please don't use this.)
I guess my objection to 9998 is the "discussion" label there when this is clearly a bug IMHO. That and my example looks way more RWC to me than the code in that ticket, although I did simplify it a bit for brevity. ;-)
The least ugly workaround seems to be this: return () => thing!.data;
That's why we need Map.prototype.emplace. You might consider using a polyfill of it.
function add_thing (id: string, data: any) {
return things.emplace(id, {insert: () => ({ data })});
}
This issue has been marked as a 'Duplicate' and has seen no recent activity. It has been automatically closed for house-keeping purposes.
This should work without the need of a new const
return () => thing!.data;
I would do like this:
interface IMap<K, V> extends Map<K, V> {
get(key: K): V;
}
type Thing = { data: any };
const things: IMap<string, Thing> = new Map();