An Introduction to JavaScript Error Handling
Tags: javascript, webdev, beginners, tutorial
Published: 2022-02-22 04:01:45

_by author [Craig Buckler](https://blog.openreplay.com/authors/craig-buckler)_

Your apps become more robust as you gain programming experience. Improvements to coding techniques play a large part but you'll also learn to consider the edge cases. If something can go wrong, it will go wrong: *typically when the first user accesses your new system*.

Some errors are avoidable:

* A JavaScript linter or good editor can catch syntax errors such as misspelled statements and missing brackets.

* You can use validation to catch errors in data from users or other systems. Never make presumptions about a user's ingenuity to cause havoc. You may be expecting a numeric integer when asking for someone's age but could they leave the box blank, enter a negative value, use a fractional value, or even fully type "twenty-one" in their native language.

  Remember that server-side validation is essential. Browser-based client-side validation can improve the UI but the user could be using an application where JavaScript is disabled, fails to load, or fails to execute. *It may not be a browser that's calling your API!*

Other runtime errors are impossible to avoid:

* the network can fail
* a busy server or application could take too long to respond
* a script or other asset could time out
* the application can fail
* a disk or database can fail
* a server OS can fail
* a host's infrastructure can fail

These may be temporary. You cannot necessarily code your way out of these issues but you can anticipate problems, take appropriate actions, and make your application more resilient.


## Showing an Error is the Last Resort

We've all encountered errors in applications. Some are useful:

*"The file already exists. Would you like to overwrite it?"*

Others are less so:

*"ERROR 5969"*

Showing an error to the user should be the last resort after exhausting all other options.

You may be able to ignore some less-critical errors such as an image failing to load. In other cases, remedial or recovery actions may be possible. For example, if you're unable to save data to a server because of a network failure, you could temporarily store it in [IndexedDB or localStorage](https://blog.openreplay.com/the-ultimate-guide-to-browser-side-storage) and try a few minutes later. It should only be necessary to show a warning when repeated saves fail and the user is at risk of losing data. Even then: *ensure the user can take appropriate actions*. They may be able to reconnect to the network but that won't help if your server is down.


## Error Handling in JavaScript

Error handling is simple in JavaScript but it's often shrouded in mystery and can become complicated when considering asynchronous code.

An "error" is a object you can throw to raise an exception -- which could halt the program if it's not captured and dealt with appropriately. You can [create an `Error` object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error/Error) by passing an optional message to the constructor:

```javascript
const e = new Error('An error has occurred');
```

You can also use `Error` like a function without `new` -- it still returns an `Error` object identical to that above:

```javascript
const e = Error('An error has occurred');
```

You can pass a filename and a line number as the second and third parameters:

```javascript
const e = new Error('An error has occurred', 'script.js', 99);
```

This is rarely necessary since these default to the line where you created the `Error` object in the current file.

Once created, an `Error` object has the following properties which you can read and write:

* `.name`: the name of the `Error` type (`"Error"` in this case)
* `.message`: the error message

The following read/write properties are also supported in Firefox:

* `.fileName`: the file where the error occurred
* `.lineNumber`: the line number where the error occurred
* `.columnNumber`: the column number on the line where the error occurred
* `.stack`: a stack trace -- the list of functions calls made to reach the error.


## Error Types

As well as a generic `Error`, JavaScript supports specific error types:

* [`EvalError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/EvalError): caused by an [`eval()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/eval)
* [`RangeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/RangeError): a value outside a valid range
* [`ReferenceError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/ReferenceError): occurs when de-referencing an invalid reference
* [`SyntaxError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/SyntaxError): invalid syntax
* [`TypeError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/TypeError): a value is not a valid type
* [`URIError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/URIError): invalid parameters passed to [`encodeURI()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/encodeURI) or [`decodeURI()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/decodeURI)
* [`AggregateError`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/AggregateError): several errors wrapped in a single error typically raised when calling an operation such as [`Promise.all()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/all).

The JavaScript interpreter will raise appropriate errors as necessary. In most cases, you will use `Error` or perhaps `TypeError` in your own code.


## Throwing an Exception

Creating an `Error` object does nothing on its own. You must [`throw`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/throw) an `Error` to raise an exception:

```javascript
throw new Error('An error has occurred');
```

This `sum()` function throws a `TypeError` when either argument is not a number -- the `return` is never executed:

```javascript
function sum(a, b) {

  if (isNaN(a) || isNaN(b)) {
    throw new TypeError('Value is not a number.');
  }

  return a + b;

}
```

It's practical to `throw` an `Error` object but you can use any value or object:

```javascript
throw 'Error string';
throw 42;
throw true;
throw { message: 'An error', name: 'CustomError' };
```

When you `throw` an exception it bubbles up the call stack -- *unless it's caught*. Uncaught exceptions eventually reach the top of the stack where the program will halt and show an error in the DevTools console, e.g.

```txt
Uncaught TypeError: Value is not a number.
  sum https://mysite.com/js/index.js:3
```


## Catching Exceptions

You can catch exceptions in a [`try ... catch` block](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/try...catch):

```javascript
try {
  console.log( sum(1, 'a') );
}
catch (err) {
  console.error( err.message );
}
```

This executes the code in the `try {}` block but, when an exception occurs, the `catch {}` block receives the object returned by the `throw`.

A `catch` block can analyse the error and react accordingly, e.g.

```javascript
try {
  console.log( sum(1, 'a') );
}
catch (err) {
  if (err instanceof TypeError) {
    console.error( 'wrong type' );
  }
  else {
    console.error( err.message );
  }
}
```

You can define an optional `finally {}` block if you require code to run whether the `try` or `catch` code executes. This can be useful when cleaning up, e.g. to close a database connection in Node.js or Deno:

```javascript
try {
  console.log( sum(1, 'a') );
}
catch (err) {
  console.error( err.message );
}
finally {
  // this always runs
  console.log( 'program has ended' );
}
```

A `try` block requires either a `catch` block, a `finally` block, or both.

Note that when a `finally` block contains a `return`, that value becomes the return value for the whole `try ... catch ... finally` regardless of any `return` statements in the `try` and `catch` blocks.

## Open Source Session Replay
Debugging a web application in production may be challenging and time-consuming. [OpenReplay](https://github.com/openreplay/openreplay) is an Open-source alternative to FullStory, LogRocket and Hotjar. It allows you to monitor and replay everything your users do and shows how your app behaves for every issue.
It’s like having your browser’s inspector open while looking over your user’s shoulder.
OpenReplay is the only open-source alternative currently available.

![OpenReplay](https://raw.githubusercontent.com/openreplay/openreplay/main/static/replayer.png)

Happy debugging, for modern frontend teams - [Start monitoring your web app for free](https://github.com/openreplay/openreplay).


## Nested `try ... catch` Blocks and Rethrowing Errors

An exception bubbles up the stack and is caught **once** only by the nearest `catch` block, e.g.

```javascript
try {

  try {
    console.log( sum(1, 'a') );
  }
  catch (err) {
    console.error('This error will trigger', err.message);
  }

}
catch (err) {
  console.error('This error will not trigger', err.message);
}
```

Any `catch` block can `throw` a new exception which is caught by the outer `catch`. You can pass the first `Error` object to a new `Error` in the `cause` property of an object passed to the constructor. This makes it possible to raise and examine a chain of errors.

Both `catch` blocks execute in this example because the first error throws a second:

```javascript
try {

  try {
    console.log( sum(1, 'a') );
  }
  catch (err) {
    console.error('First error caught', err.message);
    throw new Error('Second error', { cause: err });
  }

}
catch (err) {
  console.error('Second error caught', err.message);
  console.error('Error cause:', err.cause.message);
}
```


## Throwing Exceptions in Asynchronous Functions

You cannot `catch` an exception thrown by an asynchronous function because it's raised *after* the `try ... catch` block has completed execution. This will fail:

```javascript
function wait(delay = 1000) {

  setTimeout(() => {
    throw new Error('I am never caught!');
  }, delay);

}

try {
  wait();
}
catch(err) {
  // this will never run
  console.error('caught!', err.message);
}
```

After one second has elapsed, the console displays:

```txt
Uncaught Error: I am never caught!
  wait http://localhost:8888/:14
```

If you're using a callback, the convention presumed in frameworks and runtimes such as Node.js is to return an error as the first parameter to that function. This does not `throw` an exception although you can manually do that when necessary:

```javascript
function wait(delay = 1000, callback) {

  setTimeout(() => {
    callback('I am caught!');
  }, delay);

}

wait(1000, (err) => {

  if (err) {
    throw new Error(err);
  }

});
```

In modern ES6 it's often better to return a [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise) when defining asynchronous functions. When an error occurs, the [Promise's `reject` method](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/reject) can return a new `Error` object (although any value or object is possible):


```javascript
function wait(delay = 1000) {

  return new Promise((resolve, reject) => {

    if (isNaN(delay) || delay < 0) {
      reject(new TypeError('Invalid delay'));
    }
    else {
      setTimeout(() => {
        resolve(`waited ${ delay } ms`);
      }, delay);
    }

  })

}
```

The [`Promise.catch()` method](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch) executes when passing an invalid `delay` parameter so it can react to the returned `Error` object:


```javascript
// this fails - the catch block is run
wait('x')
  .then( res => console.log( res ))
  .catch( err => console.error( err.message ))
  .finally(() => console.log('done'));
```

Any function which returns a `Promise` can be called by an `async` function using an `await` statement. You can contain this in a `try ... catch` block which runs identically to the `.then`/`.catch` Promise example above but can be a little easier to read:

```javascript
// Immediately-Invoked (Asynchronous) Function Expression
(async () => {

  try {
    console.log( await wait('x') );
  }
  catch (err) {
    console.error( err.message );
  }
  finally {
    console.log('done');
  }

})();
```


## Errors are Inevitable

Creating error objects and raising exceptions is easy in JavaScript. Reacting appropriately and building resilient applications is somewhat more difficult! The best advice: expect the unexpected and deal with errors as soon as possible.