I'm trying to wrap my head around flow and I struggle to make it work with ES6's Map
Consider this simple case (live demo):
// create a new map
const m = new Map();
m.set('value', 5);
console.log(m.get('value') * 5)
flow throws:
console.log(m.get('value') * 5)
^ Cannot perform arithmetic operation because undefined [1] is not a number.
References:
[LIB] static/v0.72.0/flowlib/core.js:532: get(key: K): V | void;
^ [1]
I also tried:
const m:Map<string, number> = new Map();
m.set('value', 5);
console.log(m.get('value') * 5)
But I got the same error
I believe this is because flow thinks that the value can also be something else than a number, so I tried to wrap the map with a strict setter and getter (live demo):
type MyMapType = {
set: (key: string, value: number) => MyMapType,
get: (key: string) => number
};
function MyMap() : MyMapType {
const map = new Map();
return {
set (key: string, value: number) {
map.set(key, value);
return this;
},
get (key: string) {
return map.get(key);
}
}
}
const m = MyMap();
m.set('value', 5);
const n = m.get('value');
console.log(n * 2);
but then I got:
get (key: string) {
^ Cannot return object literal because undefined [1] is incompatible
with number [2] in the return value of property `get`.
References:
[LIB] static/v0.72.0/flowlib/core.js:532: get(key: K): V | void;
^ [1]
get: (key: string) => number ^ [2]
How can I tell flow that I only deal with a Map of numbers?
Edit:
Typescript approach makes more senses to me, it throws on set instead on get.
// TypeScript
const m:Map<string, number> = new Map();
m.set('value', 'no-number'); // << throws on set, not on get
console.log(m.get('value') * 2);
Is there a way to make Flow behave the same way?
What Flow is trying to tell you is that by calling map.get(key)
, .get(...)
may (V
) or may not (void
) return something out of that map. If the key is not found in the map, then the call to .get(...)
will return undefined
. To get around this, you need to handle the case where something is returned undefined. Here's a few ways to do it:
(Try)
const m = new Map();
m.set('value', 5);
// Throw if a value is not found
const getOrThrow = (map, key) => {
const val = map.get(key)
if (val == null) {
throw new Error("Uh-oh, key not found")
}
return val
}
// Return a default value if the key is not found
const getOrDefault = (map, key, defaultValue) => {
const val = map.get(key)
return val == null ? defaultValue : val
}
console.log(getOrThrow(m, 'value') * 5)
console.log(getOrDefault(m, 'value', 1) * 5)
The reason that map.get(key)
is typed as V | void
is the map might not contain a value at that key. If it doesn't have a value at the key, then you'll throw a runtime error. The Flow developers decided they would rather force the developer (you and me) to think about the problem while we're writing the code then find out at runtime.
Random and pretty late, but was searching and came up with this for my own use cases when I didn't see it mentioned:
const specialIdMap = new Map<SpecialId, Set<SpecialId>>();
const set : Set<SpecialId> = specialIdMap.get(uniqueSpecialId) || new Set();
and this saves quite a lot of boilerplate of checking if null and/or whatever. Of course, this only works if you also do not rely on a falsy value. Alternatively, you could use the new ??
operator.
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