Thursday, December 4, 2014

Lo-Dash: Using the result() function

The result() function is a handy way to look up object properties. If the property happens to be a function, result() will invoke it for us and return the result. Taken at face value, it doesn't seem like there's much to result(). On the contrary, it's a powerful tool. The basic usage looks like this:

var object = { name: 'Lo-Dash' };
_.result(object, 'name');
// → "Lo-Dash"

We probably could have just accessed the name property using the standard notation, object.name. But if what if the property doesn't exist?

_.result(object, 'enabled');
// → undefined

We get the same result we would have using the standard property access notation. Maybe undefined isn't the most desirable outcome if the property doesn't exist. What if the code that's looking up these values is performing a strict comparison?

_.result(object, 'enabled') === false;
// → false

Above, undefined is not the default we want. If the enabled property isn't defined, we want it explicitly set to false. As with the standard property access notation, we can use a logical or condition to provide a default value:

_.result(object, 'enabled') || false === false;
// → true

The problem with this approach is that even when the enabled property is there, if it's false, we're still going to get the false literal in our or condition. It gets the job done, but doesn't feel right. Starting with Lo-Dash 3.0, there's a third argument we can pass to result(). This is the default value returned when the property is undefined.

_.result(object, 'enabled', false) === false;
// → true

This approach feels a lot cleaner. Let's see how result() works with properties that are functions now.

var object = {
    name: function() {
        return this.first + ' ' + this.last;
    },
    first: 'Chris',
    last: 'Luna'
};
_.result(object, 'name');
// → "Chris Luna"

What I really like about result() is that I can substitute this object with another one who's name property is a string. Once result() is in place, it seldom needs to change. Especially now that we can provide a default value as an argument. What about accessing prototypical functions?

function Person(){}
Person.prototype.name = function() {
    return this.first + ' ' + this.last;
}
var object = new Person();
object.first = 'Hugo';
object.last = 'Sherman';
_.result(object, 'name');
// → "Hugo Sherman"

This works because result() looks up properties using standard property access notation, and doesn't check if the property is an own property. And because of this, we're able to result() to look up array items too:

var array = [ 1, 2, 3 ];
_.result(array, 0);
// → 1

With Lo-Dash 3.0, we can use this approach and provide a default value to use when an invalid index is specified.

_.result([], array.length - 1, 0);
// → 0

Since result() is a function, we can utilize it in places other than where we need property access. For example, as a partial:

var fifo = [],
    put = _.bind(fifo.push, fifo),
    get = _.partial(_.result, fifo, 'shift'));

put('a');
// → [ "a" ]
get();
// → "a"
fifo;
// → []

The put() function is bound to fifo as the context. So is get(), because it's partially-applied to the result() function as an argument. As you can see there are plenty of interesting use cases for the result() function, small as they may be.