Programmers have been plagued by having to write numerous checks for null values ever since the dawn of time. Consider this example,
var customer = database.get(id)
var customerLastName = customer.name.last
What if I told you that, any of customer
, customer.name
, and customer.name.last
could be null
at runtime? And if any of them are null
just set customerLastName
to "Unknown"
. You’d probably do something like
var customerLastName = "Unknown"
var customer = database.get(id)
if (customer && customer.name && customer.name.last){
customerLastName = customer.name.last
}
This is an awful lot of repetitive code, and you really don’t want to be writing it over and over again. But, what’s worse than having to write it, is actually having to read it later on.
To see the amount of repetition in the above code, let’s break down the word frequency. The top repeated words are
customer
: 5name
: 3last
: 2customerLastName
: 2
If you count phrases too,
customer.name
: 3customer.name.last
: 2
There are actually numerous hacks out there already dedicated to solving this problem for ruby, groovy, and other various programming languages.
In this post, I am going to investigate two approaches to tackling this in Javascript.
Approach 1
Because null
isn’t an object in Javascript, approaches like andand won’t work. So, what do we do when we want to extend something but can’t modify it? Right! We wrap it! This approach is called NullSafe. Basically, like jQuery, you wrap the object you want to query, and then chain method calls to it until you get the result you are looking for. At the end, you have to unwrap it to get the computed value. What I came up with is this:
var customer = database.get(id)
var customerLastName = NullSafe(customer)._('name')._('last').val() || "Unknown"
Actually, I made it a little more powerful than that, even. In addition to getting an object’s properties, you can also call methods:
var customerLastName = NullSafe(database).$('get', id)._('name')._('last').val() || "Unknown"
Then I added just a bit more convenience with the variable length arguments for getting a chain of properties:
var customerLastName = NullSafe(database).$('get', id)._('name', 'last').val() || "Unknown"
How is this? Is this good? The syntax takes a little getting used to, but the advantage is that you only have write each thing just once, rather than the big repetitive mess you saw above. Also, chaining allows you to write everything in one line.
The implementation of NullSafe
is pretty simple too, all but 30-plus LOC,
function NullSafe(obj){
function NullSafe(obj){
this.obj = obj
}
Wrapper = NullSafe
var proto = Wrapper.prototype
proto.call = proto.$ = function(){
var name = arguments[0]
var args = Array.prototype.slice.call(arguments, 1)
var obj = null
if (this.obj){
var func = this.obj[name]
if (this.obj && func){
obj = func.apply(this.obj, args)
}
}
return new Wrapper(obj)
}
proto.get = proto._ = function(){
var curr = this.obj
for (var i = 0; i < arguments.length; i++){
if (curr != null){
curr = curr[arguments[i]]
}
}
return new Wrapper(curr)
}
proto.val = function(){
return this.obj
}
proto.toString = function(){
return String(this.obj)
}
return new Wrapper(obj || this)
}
But then you might wonder why in the world I didn’t just use a try
-catch
block?
var customerLastName = "Unknown"
try{
customerLastName = database.get(id).name.last
}catch(e){}
Approach 2
As you saw, the try
-catch
approach isn’t so bad. It is a bit verbose, and spans multiple lines, but we can fix that.
var customerLastName = $try(function(){return database.get(id).name.last}) || "Unknown"
Pretty nice! Although the actually code is slightly longer than NullSafe, this is easier to understand because within the inline function you are just writing what you would have normally written anyway if you hadn’t had to do the null checks.
The implementation is even simpler,
function $try(func){
try{
return func()
}catch(e){
return null
}
}
Performance
I normally don't prematurely optimize, but with jsperf.com, I thought I’d be fun to compare. Intuitively, $try
should be faster because it’s doing less, but then again, exception handling can be slow.
I created two separate tests, one in which the chain would have values all the way through to the end, and another in which it would null-out early.
Results
The results were - drum roll please - $try
was significantly faster in almost all browsers, the only exceptions where is the null-out early case for Chrome and Firefox, where it was only slightly slower - 20% - 40%. But in all the other cases, $try
was faster, in some cases, much faster - almost 7 times in Safari.
Conclusion
With that, I will conclude that $try
is my preferred approach, given that it is easier to implement, produces easier to understand code, and executes faster that NullSafe
.