ArticlesEngineering

Nothing

The complexities of the absence of a value in JavaScript.

In JavaScript, we have tons of foot guns. I was recently asked how to best convey the absence of a value.

My immediate reaction was, "Definitely not exceptions." Looking at a function signature will not let us know to handle that case. Placing try/catch syntax around calls is tedious in JavaScript as it is procedural, not expression based. Exceptions have their place, but should not express common or expected behavior.

The next question was null or undefined. Null means "no object" and undefined means "not assigned." Neither of these exactly means nothing. Before choosing, it is worth knowing the differences.

The type of null is object, the type of undefined is undefined. Choosing undefined, a type check would blow up faster.

Null key values are preserved in JSON serialization, undefined is not. Choosing undefined, a key set to undefined would not be present in JSON. (Interestingly, JSON.stringify casts undefined within an array to null.)

Default values for arguments and destructuring only are used when undefined. Choosing undefined, default values would be used.

These differences do not provide a clear answer. Sometimes we don't want to blow up on type checks since most JavaScript developers do know the type of null is object. Sometimes we would like to see a key in JSON to know something could be there. Sometimes we do not want default values to apply unless we are truly passed fewer arguments.

So it boils down to what is idiomatic for the absence of a value. Undefined is the idiomatic choice based on the standard data structures. The value of a key in an object that does not exist is undefined. The value of an index in an array that does not exist is undefined. If Array.find has no match, it returns undefined.

Undefined is the idiomatic nothing for JavaScript, but can we do better? Given only these two options we should pick undefined, but there is another option. Our issue most often is one of ambiguity where the values we deal in can be anything, including nothing. If our consumer is dealing in undefined values, we would still be clobbering their data.

Left to their own devices, consumers would most likely encapsulate all of their values in some data wrapper. This wrapper would hopefully allow us to process their values and allow them to pluck back the original values. The consumer is using envelopes to work around our ambiguity.

We can do better by the consumer by returning union types. In the case of returning nothing, we'd need something like a "Maybe" type. Maybe we have a value or we have nothing to return.

We can return a nothing object {type: 'nothing'} or value object {type: 'something', value}. The caller must anticipate the possibility of an absence of a value since they will be forced to check the type. The value can be anything since nothing is its own type.

Union types solve ambiguity, but are they idiomatic JavaScript? The Maybe type has yet to see any strong mainstream implementation.

Another common union type Result has existed in JavaScript for some time. Result encapsulates either an error or a value. We can trace JavaScript's Result all the way to callback hell. The Node-style callback signature (error, value) was the start. Over the years, Promises came to reflect the same in resolve/reject. It has not progressed to being a standalone/non-async value yet.

While union types solve the problem best, they are not yet idiomatic.

For those of us writing JavaScript today, we are in a bit of a pickle. If we choose undefined, we embrace what is idiomatic at the sacrifice of ambiguity. If we choose union types, there is no ambiguity but we lack an idiomatic bedrock to build upon. Languages with common union types have many helpful utilities to assist in handling them. JavaScript has no such support system at the moment.

Unfortunately, we have to use our best judgement in approaching these problems. Unfortunately, since this problem is both so common yet so complex we will keep getting it wrong.

Ultimately, if we want to solve this problem we will need union types at the language level. If this sounds unappealing, I can sympathize. JavaScript continues to grow and I'll be the first to admit I don't like many additions. However, I come to this conclusion after years of it being a known problem solved many times in libraries.

Searching for Result and Maybe will reveal a land of misfit union types, none of which have won out. It is hard to claim victory when the solutions are so simplistic and easily hand-rolled. Everyone wants a bit more of their own functionality, less of anything else. The community has not chosen a strong bedrock and we all suffer.

I think this is a problem worth addressing with a formal TC39 proposal. I have created a TC-39 Strawman proposal to start developing these ideas in hopes of proposing idiomatic union types and getting this effort championed. If interested in shaping this proposal please reach out.