Blog

Spot the mistake

Let’s start by having a look at this:

Introduction

When you read this, is there anything that catches your attention? Well, there are 5 errors hidden in this code that can be hard to find if you don’t know what you are looking for. I wanted to share some mistakes I made when as started working with JavaScript that anybody could do and spend an hour trying to figure them out.

Expressions vs Statements

const throwError = message => throw new Error(message)

At the beginning of the snippet, I wrote an arrow function that throws an error. The message argument is not surrounded by parenthesis, neither is the implementation surrounded by curly braces. But you could do that since it’s a one argument one line arrow function, right? Well not really: while you can always remove the parentheses when there is only one argument, you cannot always do that when the implementation is in one line. You can only do that with expressions but throw is not an expression, it is a statement that is a very subtle difference but that will throw the error every single time you try to run the function.

In fact, the first line is equivalent to this:

const throwError = message => return throw new Error(message)

And this does not work. What we should have done is to put curly braces around the statement like that:

const throwError = message => {throw new Error(message)}

But then this looks an awful lot like the second line doesn’t it?

const createResult = (type, result) => {type, result}

And when we try using it, we can see it always returns undefined. This is because it does not create an object as we want it to, but it tries to interpret it as a statement. What we want is to return a value and for that, we need to surround the object with parentheses, so JavaScript knows what we are trying to do:

const createResult = (type, result) => ({type, result})

Automatic semicolon insertion

JavaScript is nice and it automatically adds semicolons to the end of statements — not to the end of the line — which allows for line breaking in long lines. The automatic semicolon insertion (ASI) adds a semicolon at the end of statements like:

  • an empty statement
  • a var, let or const statement
  • an expression statement
  • a break statement
  • a return statement
  • a throw statement

So this part of the code:

ends up looking like this:

Do you see the problem here? There is no semicolon insertion in the middle of the result assignment because result alone is just an expression, so the ASI waits for the end of the statement, but return is not an expression. It is a statement by itself, so the ASI will add a semicolon to the end of the line and the default object is never created.

What is a ‘number’ ?

The importance of parentheses

The last mistakes are hidden in the final conditional construct. Obviously, this line will tell us if the result is a number, right?

typeof result !== "number

Let’s put it in the console and see the result. It should be either true or false. But the result we receive is "Boolean"! What happened here?

This happened because there are no parentheses to tell typeof what operand is there, so it takes everything that follows it. In this case, interpretation is as follows:

typeof (result !== "number")

To have the correct value we need to manually add the parentheses around typeof’s operand:

typeof(result) !== "number"

Type of NaN

Once we are done, we can add, subtract, divide and multiply any number… But wait! There is an edge case that we did not take into account: the case of NaN.

> calculate("add", 3, "foo")
red : Uncaught Error: not a number
> calculate("mult", 3, "foo")
{type: "mult", result: NaN}

What happens if we try to add a string and a number? We’ll get a string and this is caught by the end condition which throws an error.

What happens if we try to divide a string by a number ? We get NaN, which stands for Not a Number. And what is the type of NaN? You might think this could be anything but a ‘number'. This is indeed confusing. NaN is of type number so it will not throw the error and return the object as if everything went well. The code needs to check if the result value is a number and is not a NaN :

if ((typeof result) !== "number" || !isNaN(result))
 throwError("not a number")
else
 return createResult(type, result)

JavaScript has a lot of weird edge cases and strange syntax exceptions. This is mainly due to the fact that unlike languages like Python, JavaScript keeps compatibility with scripts written with older versions of JavaScript. Errors just add up and cannot be changed once they have been implemented, this leads to a lot of inconsistencies in the way code behave that can make it hard to debug. I hope you enjoyed looking for these errors in my code and that you have learned at least something about edge cases in JavaScript.

Image for post


Read more from this Author
No items found.