Understanding Exception Handling in JavaScript
Tags: javascript, fundamentals
Published: 2022-01-18 07:47:20

---
title: Understanding Exception Handling in JavaScript
published: true
description: 
tags: javascript, fundamentals
//cover_image: https://direct_url_to_image.jpg
---

When writing code, we’re make assumptions, implicitly or explicitly.

As an example, let say you wrote a simple `multiply` function like below:

```js
function multiply(x, y) {
  return x * y;
}
```

`multiply` function has an implicit assumption that both parameters (`x` and `y`) are both numbers.

```jsx
// When the assumption is correct, all is fine.
multiply(2, 5); // -> 10

// When the assumption is incorrect
multiply('abcd', '234'); // -> NaN
multiply(new Date(), 2); // -> 32849703863543284970386354
```

## How to Handle Incorrect Assumption

Although the example above seems trivial, its impact may not so trivial once you realize that a simple function could be used for important operation, such as calculating how much your customer pay you:

```jsx
function getCartData() {
  // here we always return the same result, but just imagine that
	// in real life, it call some API to get the data
  return { 
		items: [{ quantity: 2, unitPrice: 50 }, { quantity: 1, unitPrice: 'USD 5' }]
	};
}

function issueInvoice(total) {
  // some code that will generate invoice to customer
}

function getCartTotal(items) {
  let total = 0;

  for (const item of items) {
    /* one of the item.unitPrice is 'USD 23.00' 
    (instead of number),
    which will cause the total become NaN */
    total += multiply(item.unitPrice, item.quantity);
  }

  return total;
}

function chargeCustomer(cart) {
  const total = getCartTotal(cart.items);
  // when total is NaN, issueInvoice 
  // will generate invoice with 0 amount!
  issueInvoice(total);
}

function checkout() {
  const cartData = getCartData();
  chargeCustomer(cartData);
}
```

To properly fix the issue, we need to fix the code that incorrectly set the `unitPrice` as `'USD 23.00'` instead of `23`. However, sometimes the code that generates the data is out of our control, e.g. it could be maintained by other team, or it could be code from other company.

So how do we deal with incorrect assumption in code?

### 1. Assume less

The first approach of dealing with assumptions is to eliminate them.

We can change our `multiply` function to below:

```jsx
// multiply will returns undefined if either parameter is not number
function multiply(x, y) {
  if (typeof x !== 'number' || typeof y !== 'number') {
    return undefined;
  }
  return x * y;
}
```

And then the code that calls `multiply` should handle both `number` and `undefined` as returned result of the call.

```jsx
// getCartTotal will returns undefined if the computation could not complete
function getCartTotal(items) {
  let total = 0;

  for (const item of items) {
    const subtotal = multiply(item.unitPrice, item.quantity);
    if (typeof subtotal === 'undefined') {
	    alert(`Subtotal is not number, something is wrong!`);
      return undefined;
    } else {
	    total += subtotal;
    }
  }

  return total;
}

function chargeCustomer(cart) {
  const total = getCartTotal(cart.items);
  // when total is undefined, issueInvoice will not be run
  if (typeof total === 'undefined') {
		issueInvoice(total);
  }
}
```

As you may already observed, although _assuming less_ works, but it makes the code more complicated as there are more conditional logics now.

### 2. `throw` Error

Fortunately, JavaScript (and most modern programming languages) allows us to handle exception case like above using `throw`, for example

```jsx
function multiply(x, y) {
  if (typeof x !== 'number' || typeof y !== 'number') {
    throw 'parameters passed to multiply function is not number.';
  }
  return x * y;
}
```

Now that when `multiply` is called with either of the parameters is not number, you will see the following in the console, which is great.

![Browser console showing error Uncaught parameters passed to multiply function is not number.](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8s00oxl2h3kz0yj1l2ii.png)

More importantly, `throw` will stop the code execution, so the remaining code will not run.

```jsx
function getCartTotal(items) {
  let total = 0;

  for (const item of items) {
    /* one of the item.unitPrice is 'USD 23.00' (instead of number),
    which will cause multiply to throw */
    total += multiply(item.unitPrice, item.quantity);
  }

  return total;
}

function chargeCustomer(cart) {
  const total = getCartTotal(cart.items);
  // the following code will not be reached, 
  // as throw will stop the remaining code execution
  issueInvoice(total);
}
```

Now customer will no longer be getting free stuffs anymore!

## Handle error gracefully

Although now we stop the code from giving away free stuffs to customer by using `throw`, but it would be even better if we can provide a more graceful behavior when that happens, like showing some error message to customer.

We can do that using `try ... catch`.

```js
function getCartTotal(items) {
  let total = 0;

  for (const item of items) {
    total += multiply(item.unitPrice, item.quantity);
  }

  return total;
}

function chargeCustomer(cart) {
  const total = getCartTotal(cart.items);
  issueInvoice(total);
}

function checkout() {
  try {
    const cartData = getCartData();
    chargeCustomer(cartData);
  } catch (err) {
    // log to console. But can send to error tracking service 
    // if your company use one.
    console.error(err); 
    alert('Checkout fails due to technical error. Try again later.');
  }
}
```

Now customer will see an error message, instead of just page not responding.

To visualize the code flow, you can refer to the following drawing.

![Error handling flow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6wtgywfyv19swkdso7cs.png)

## Best Practices on Using `throw` with `try ... catch`

### 1. Only use it for exceptional case.

Compared to other conditional syntaxes like (`if` and `switch`), `throw` and `try ... catch` are harder to read because the `throw` statement and `try ... catch` may be in totally different part of the codebase.

However, what is considered to be “exceptional” case  depends on the context of the code. 

1. For example, if you are writing user-facing code that read the user input, don’t use `throw` to control the logic to validate user input to show error message. Instead, you should use normal control flow like `if .. else`.
2. On the other hand, if you are writing computation code like calculation, you can use `throw` when the data passed to you is invalid, as we usually assume input is validated on the more user-facing code, so when an invalid data reach you, it probably is some programmatic mistake that rarely happens. 

### 2. `throw Error` only

Although technically `throw` any value like `string` or `object`, it is common practices to only `throw` [`Error`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error).

```js
throw new Error('Something goes wrong that I not sure how to handle');
```

### 3. Always `console.error` in `catch`.

It is possible that the `try ... catch` phrase will catch error that is thrown by other code. For example:

```jsx
try {
  let data = undefined;

	if (data.hasError) {
		throw new Error('Data has error');
  }
} catch (err) {
  console.error(err);
}
```

At first glance, you may thought that the `err` in `catch` phrase is the error thrown with `throw new Error('Data has error')`. But if you run the code above, the actual error being thrown is `TypeError: Cannot read properties of undefined (reading 'hasError')`. This is because the line `if (data.hasError) {`  tries to read property from `undefined`. 

Those runtime JavaScript error will be caught by `try ... catch` as well, so it’s best practices that you always `console.error` the error to ensure that you are aware of those unexpected programming error.