Lazy and async assertions

Flexible assertions without performance penalty.

I use assertions in my production code for two reasons

  • Users always find new ways to go beyond what I expect and test for. By using lots of assertions and reporting any errors to Sentry servers I get the detailed exception information right away.
  • The assertions document and explain the source code.

Recently I looked at several JavaScript assertions libraries. I wanted to pick something flexible and easy to use for my day to day production code. Ultimately we picked check-types.js and are pretty happy with its syntax.

There is a huge limitation in the assertions libraries I looked at. Even if they allow a message to be passed with the condition, it is at most a single string.

assert(condition, message);

I want as many details in the failed assertion as possible. Preferably enough details to allow me to diagnose the problem. I often find myself concatenating multiple variables into the message string

1
2
assert(condition, 'Something went wrong, foo was ' + JSON.stringify(foo, null, 2)
+ ' environment state is ' + environmentInfo());

If the condition fails, this assertion will give the developers plenty of information to diagnose the problem, but it:

  1. Looks ugly and is pain to write.
  2. This is very very computationally expensive and you pay the performance penalty every time, even if the condition is true (as it should be almost every time).

Lazy assertions

I waned to solve both problems, so I wrote lazy-ass function.

  • You can have as many message arguments after the condition as needed.
  • The message arguments could be strings, objects or functions.
  • Only the condition is evaluated every time, the arguments are ignored if the condition is true.

The same example as above would looks like this:

lazyAss(condition, 'Something went wrong', foo, 'environment state is', environmentInfo);

If the condition is false, lazyAss forms the error message: calls any message function, stringifies any objects, concatenates the strings.

You can use check-types with lazy-ass:

lazyAss(check.unemptyString(foo), 'expected foo to be unempty string', foo);

Async assertions

Once you see the errors reported from live production environment, you will discover that things are often not how you expect them to be. You start practicing defensive or even paranoid programming. Using lots of assertions without performance penalty is nice, but sometimes you do not want to throw an exception on failed assertion - it breaks the execution flow: parts of the application end up in invalid state, which is hard to recover from.

You still want to throw an exception to let you know about the broken assumption. You can throw an error by scheduling it onto the event loop - this will let the current code stack to finish executing and then will throw an error. Ordinarily you would just see the error in the browser's console in this case, but if you have global error handler installed, it can forward the error to Sentry for example.

setTimeout(function () {
  throw new Error('Something went wrong');
}, 0);

To avoid the boilerplate code, and to follow same assertion syntax, lazy-ass includes the async assertion function

lazyAssync(condition,
  'Something went wrong, foo', foo, 'environment', environmentInfo);

This will throw the error asynchronously with full message formed only if the condition is false.

Short code

You can use lazy assertions yourself by pasting the following code. It even tries to stringify objects when possible. It does not handle exceptions, regular exceptions and arguments structures.

1
2
3
4
5
6
7
8
9
function la(predicate) {
if (!predicate) {
var msg = Array.prototype.slice.call(arguments, 1)
.map(function (x) {
return typeof x === 'string' ? x : JSON.stringify(x);
}).join(' ');
throw new Error(msg);
}
}