Yesterday I ran into this StackOverflow question and it got me thinking about null
/undefined
handling in javascript.
A short background
What is undefined
? undefined
is a primitive value that is given to variables that have only been declared, non-existent properties or function arguments
What is null
? null
is another primitive value that represents the absence of value.
So what happens when we do the following
let obj;
console.log(obj.someProp);
We get the following error
TypeError: Cannot read property 'someProp' of undefined
And the same happens with null
null-checking
So, how can we avoid that? Well, lucky for us in javascript we have short-circuit
evaluation, meaning that in order to avoid our previous TypeError
we could write the following.
let obj;
console.log(obj && obj.someProp); // Prints undefined
But what if we want to go deeper, something like obj.prop1.prop2.prop3
? We would end doing lots of checks, like:
console.log( obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3 );
Seems obnoxious, doesn't it?
And what if we wanted to print a default value if there was an undefined
or null
in that chain? Then it would be an even larger expression:
const evaluation = obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3;
console.log( evaluation != null ? evaluation : "SomeDefaultValue" );
How do other languages do it?
This issue is not exclusive of javascript, it is present in most programming languages, so let's see how to do null-checking in some of them.
Java
In java we have the Optional
API:
SomeClass object;
Optional.ofNullable(object)
.map(obj -> obj.prop1)
.map(obj -> obj.prop2)
.map(obj -> obj.prop3)
.orElse("SomeDefaultValue");
Kotlin
In kotlin (another JVM language) there are the elvis (?:
) and safe-call (?.
) operators.
val object: SomeClass?
object?.prop1?.prop2?.prop3 ?: "SomeDefaultValue";
C#
Finally, in c# we also have the null-condition (?.
) and null-coalescing (??
) operators.
SomeClass object;
object?.prop1?.prop2?.prop3 ?? "SomeDefaultValue";
JS Options
So after seeing all this, I was wondering, isn't there a way to avoid writing that much in javascript?, so I started experimenting with regex to write a function that'd allow me to access an object property safely.
function optionalAccess(obj, path, def) {
const propNames = path.replace(/\]|\)/, "").split(/\.|\[|\(/);
return propNames.reduce((acc, prop) => acc[prop] || def, obj);
}
const obj = {
items: [{ hello: "Hello" }]
};
console.log(optionalAccess(obj, "items[0].hello", "def")); // Prints Hello
console.log(optionalAccess(obj, "items[0].he", "def")); // Prints def
And after that, I found about lodash._get
, which has the same signature:
_.get(object, path, [defaultValue])
But to be honest I'm not that much a fan of string paths so I started searching a way to avoid them, then I came with a solution using proxies:
// Here is where the magic happens
function optional(obj, evalFunc, def) {
// Our proxy handler
const handler = {
// Intercept all property access
get: function(target, prop, receiver) {
const res = Reflect.get(...arguments);
// If our response is an object then wrap it in a proxy else just return
return typeof res === "object" ? proxify(res) : res != null ? res : def;
}
};
const proxify = target => {
return new Proxy(target, handler);
};
// Call function with our proxified object
return evalFunc(proxify(obj, handler));
}
const obj = {
items: [{ hello: "Hello" }]
};
console.log(optional(obj, target => target.items[0].hello, "def")); // => Hello
console.log(optional(obj, target => target.items[0].hell, { a: 1 })); // => { a: 1 }
The future
Currently, there is a TC39 proposal that will allow us to do the following:
obj?.arrayProp?[0]?.someProp?.someFunc?.();
Looks pretty neat right? However, this proposal is still in stage 1, which means it will probably take a while before we can see this is js. Nonetheless, there is a babel plugin that allows us to use that syntax.
Finale
null
has been and will be around for a while and I bet is one of the most hated concepts in programming, however, there are ways to procure null-safety. Here I've posted my two cents, let me know what you think or if you have any other alternatives.
p.s.: Here is a little pretty gist
Top comments (5)
there is a babel plugin that can solve your problem
I think it is
@babel/plugin-proposal-optional-chaining
Yeah, I know about it, but the idea is to suggest a solution that doesn't require external plugins.
Thanks or the post!
As of December 2019 the proposal has reached the final stage:
github.com/tc39/proposals/blob/mas...
I'm really excited 😁
According to that link, it's in Stage 3 as of the writing of this comment.
Finally on Stage 4...