The (Synchronous) Pyramid of Doom in JavaScript

py

Let’s say we own a bakery, and our stock system gives a JSON output of delicious things in stock, organised neatly into categories. Something like this:

Somewhat irritatingly, the stock system excludes zero-stock items, and categories containing only zero-stock items. So there’s no guarantee, for example, that stock.bread.sweet.banana will exist at any one time, or for that matter stock.bread.sweet if both banana and barabrith are 0.

Under a scenario where we simply want to list things that are in stock, this is not a problem. To display all items in stock, simply traverse the object by recursively iterating its own properties, listing the items along the way.

Let’s say we decide that banana bread should be on sale, and we want to display a message. Something like the following is added to our website’s JS file.

Later on, we check if numBanana > 0 and display the sale message.

Look at the expression within the if. If banana bread is not in stock, but our sweet category still exists within the object, then the if evaluates to false since type stock.bread.sweet.banana === 'undefined'.

If however all items within sweet fall out of stock, and thus the sweet object drops out of bread entirely, then we get the dreaded TypeError: undefined is not an object (evaluating 'stock.bread.sweet') whilst attempting to look up banana inside an object that does not exist.

The most simple solution, of course, is to perform some nested checks:

This kind of code is often referred to as the Pyramid of Doom (or sometimes the Triangle of Doom), whereby the developer safely step towards the leaves of a nested structure and finally does something if everything passes.

It’s also commonly seen in JavaScript asynchronous code when the developer attempts to chain together multiple AJAX calls, each depending on the result of the previous. These days, we can just use Promises to solve that specific problem. I have actually added (Synchronous) to the post title as many people associate the pyramid in JavaScript with this specific pattern.

Intuitively, there should be a cleaner way of achieving the above simple case since it’s such an incredibly common operation to access deeply-nested data using dot notation.

In Other Languages

Many other languages feature a dedicated operator to solve this problem. The operator is often referred to as the safe navigation operator.

If you’re using Coffeescript, safe navigation can be achieved using ?.. This operator allows a large chained dot-expression to be built, safe in the knowledge that the whole expression evaluates to undefined if the path fails at any point.

The logical-OR operator is used at the end to provide a default value in the case of undefined, since undefined evaluates to false inside a conditional.

Groovy and Ruby also offer a safe navigation operator, with Groovy opting for the standard ?. and Ruby opting for &..

In addition, Groovy also provides the brilliantly-named Elvis operator, ?:, which is a short version of the ternary expression bool-exp ? true-exp : false-exp that we expect from most modern languages. The Elvis operator can be used to provide the default value after a safe navigation:

The popular Angular framework, version 2, provides the safe navigation operator as part of its templating language.

In additional to the safe navigation operator, Swift offers multiple expressions within a let statement, allowing a neat way of successively unwrapping the path:

Interestingly, in Objective-C, the usual dot notation is quite safe as the language is message-based, and messages sent to a null object are simply ignored.

Several other languages offer slightly clunky higher-level library solutions, such as Java’s Optional class. Such constructs are inevitably useful, but not quite worth the additional verbosity. In any case, higher-level solutions are available in almost any language if you’re willing to write them.

Flattening the Pyramid in JavaScript

But what about in JavaScript? Is there any hope?

There are a few solutions to referencing an object using an uncertain path. Practically, there are actually two common use-cases here:

  • Evaluating whether a path into the object is valid; for use in a conditional; and
  • Evaluating an expression to a default value (e.g. 0) should the path be invalid; else evaluating to the value at the valid path.

Let’s take a look at some of the solutions offered. As a quick spoiler, none of these solutions are as nice as having a dedicated operator, but there is some hope for the future.

Short Circuit Evaluation

If we wanted to be a bit more terse with respect to line count, a flatter version could be written using a short circuit evaluation expression.

This is safe because the list of conditionals bound by logical-AND are evaluated strictly from left to right. The moment one conditional fails, the entire expression evaluates to false and no more conditionals are tested.

It is slightly more readable than the pyramid solution. But what if the path specification was larger, or the nesting deeper? We could up with quite a large horizontal expression. Should we choose to break it onto multiple lines, we might as well just opt for the pyramid.

Also, we are assuming that evaluating the object is cheap. If stock.bread was actually a DOM lookup, then it would require three DOM lookups in the worst case before we could even use the result! This is not ideal because DOM lookups are usually quite expensive, involving traversing large dynamically-changing hierarchies. Perhaps the browser in this case would smartly cache the element. But we shouldn’t rely on it.

Merge with a Full ‘Default’ Object

This solution assumes that you know what the full object should be in some default state. You take this ‘default’ object, which in our case would be the full menu with all leaf items set to 0, and then recursively merge it into the partial object. Any of the full object’s keys that are undefined in the partial object are copied over to the partial object.

The assumption that we always have the full object is of course often a large one to make, and incurs the additional overhead of maintaining the full object somewhere.

If you use Lodash or Underscore, then these libraries already offer a _.defaults(destObj, [sourceObjs]) function that provide this functionality.

Dedicated Function

It’s easily possible to write a function that takes in an object path as a string (with standard dot notation) and provides a boolean indicating whether this path truly exists or not.

Here’s a quick and dirty way of achieving this, with minimal error-checking:

Now we can just test for the path’s existence before attempting to evaluate it within the object:

This function is trivially adapted to instead provide the user with the value at the path, or else some default value if the path is invalid.

Note that isValidPath shouldn’t be used in production code, but you get the general idea. A much more robust and flexible version of this function is provided in the form of Lodash’s _.get(object, path, [defaultValue]) function, which can take a string or array as the path, can evaluate array indices, and provides that useful default value in the case of failure.

The Future

There have been some discussions on the es-discuss mailing list into adding a safe navigation operator to future versions of ECMAScript. However, the difficulties of bolting this operator’s semantics into the language whilst maintaining backwards compatibility seem to have stalled the discussions for the time being.

I have no doubt that such an operator will make it into the specification at some point in the future, given how incredibly useful such operators prove to be in other languages. In the meantime, I would personally recommend using a pre-built solution such as Lodash’s _.get, or for more power, object-path by Mario Casciaro. Both of these provide a relatively terse manner of achieving the result we’ve been looking for here.

If you’re interested, Bara Brith is a traditional Welsh fruit loaf, made with tea. It is absolutely delicious and recommended even if you don’t really like fruit cakes.

Featured image is a modified version of the following image under CC BY-SA 3.0 licence: https://commons.wikimedia.org/wiki/File:Kheops-Pyramid.jpg