JavaScript Errors: An Exceptional History - Part II
Tags: javascript, beginners, webdev
Published: 2019-11-26 15:05:58

Hello again! Welcome to the finalé of a two-part series of posts on errors in JavaScript.

Last time, we took [a look into the history of errors in JavaScript](https://blog.appsignal.com/2019/10/17/javascript-errors-an-exceptional-history.html) — how JavaScript shipped without runtime exceptions, how error handling mechanisms were later added both to the fledgeling web browsers of the day and to the ECMAScript spec, and how they future efforts to standardise these features would be connected to the politics of the browser wars of the late 90's and 2000's.

This time, we'll focus a little more on the state of affairs in JavaScript today. We'll look at the different ways you can handle errors in your app today, the various idiosyncrasies they have, and how you can use our JavaScript client library to report errors from your app to our dashboard.

Let's do it!

## Handling Errors Today

After the last post, you may be forgiven for thinking that handling errors gracefully in JavaScript might be a bit of a nightmare. Luckily, it's not so daunting a prospect as it might seem, but there are quite a few different ways to handle errors with varying levels of scope and different use cases.

### `window.onerror` Handler

The `window.onerror` handler exists today in all modern web browsers as a means to catch uncaught exceptions from the current `window`. Any thrown error that is not otherwise handled in a `try`/`catch` block will be passed to the handler as the first argument to that function. The current `window` refers to the current global context, so it's important to note that `<iframe>`s and Web Workers (for example) will have their own `window` context.

> When we use a `window` in the following examples, we're referring to the global `window` context of the browser.

By assigning a function to `window.onerror`, we can write custom logic to handle any uncaught exceptions that are thrown during the lifecycle of our application:

```ts
// NOTE: using typescript syntax here in order to show what types the arguments are

function onError(msg: string | Event, source?: string, lineno?: number, colno?: number, error?: Error) {
// error handling code here!
}

window.onerror = onError;
```

You might notice that some of these arguments are marked as optional. This is because, as you might guess, browsers disagree on the number of arguments passed to the `onError` handler. Browsers as recent as Safari 9, for example, do not pass an `Error` object as its fifth argument. Internet Explorer 9 passes neither the `colno` or `error` arguments. Because of this inconsistency, care needs to be taken when writing an `onError` handler that works in older browsers.

However, thanks to the existence of the `Error` object in most modern browsers, you can normally rely on that 5th argument to be present, which will include some useful information that might come in handy when debugging, such as the current stack trace (`error.stack`). 

> As this handler tends to be a little noisy (thanks, browser extensions...), the AppSignal JavaScript library doesn't automatically catch exceptions passed to the `window.onerror` handler. Instead, we have packaged this functionality in an optional plugin — the `@appsignal/plugin-window-events` package on `npm`.

As a convenience, once the `onError` handler is called, most browsers will call `console.error` behind the scenes to display the `Error` object (often including its stacktrace) in the console.

The Document Object Model Level 2 specification introduced the `EventTarget` interface to provide a generic way to bind event listeners to an `Element` (or other objects like `Document` and `Window`) that worked cross-browser, but also added features like the ability to have multiple handlers bound to an event. This means that many of the older event handlers, such as our friend `onError`, received a modern facelift.

```js
window.addEventListener("error", function(event) {
  // error handling code here!
});
```

In this example, you can see that the `event` of type `ErrorEvent` is passed as the single argument to your callback. The `event` object contains both the information about the error but also the event itself, but again, older browsers differ in the information they provide in the `event`.

### `try`/`catch` Operator

For synchronous code, the humble `try`/`catch` operator remains the most common way to handle exceptions. As we discussed in the previous post, `try`/`catch` exception handling allows you to *try* executing a block of code that may *throw* errors at runtime; if it does, the exception is then *caught* by the `catch` block, allowing us to control what happens and what state our app is left in.

While it's certainly true that JavaScript still allows you to throw any value as an exception, community convention has filled the gap where the ECMAScript specification leaves ambiguity; it is more commonplace to receive `Error` objects as the argument to the `catch` block nowadays, and good library implementors [will generally throw `Error` objects for you to handle](https://github.com/goldbergyoni/nodebestpractices/blob/master/sections/errorhandling/useonlythebuiltinerror.md).

```js
try {
  throw new Error("I'm broken");
  // generates an exception
} catch (e) {
  // statements to handle any exceptions
} finally {
  // clean up
}
```

In the `catch` block, you should add any code that allows you to put your app back into a *defined state*. 

React's documentation for their Error Boundaries feature explains the problem well from a UI perspective, and the same is also true for exception handling as a whole:

> For example, in a product like Messenger leaving the broken UI visible could lead to somebody sending a message to the wrong person. Similarly, it is worse for a payments app to display the wrong amount than to render nothing.

It's also a good idea to log your exception somewhere — failing silently is rarely useful, your aim here is to surface the exception as best as you can to debug problems before they become a problem for the user.

> In your code, the `catch` block is where you would want to include your call to `appsignal.sendError()` if you're using our JavaScript library. Here, you can pass the `Error` object as an argument and have it appear in your Errors dashboard.

The `finally` block tends to not be as useful in JavaScript as it is in other languages. In the `finally` block, normally should try to clean-up any resources created before the exception was thrown, however as JavaScript is a garbage-collected language and resources are allocated and de-allocated dynamically, we often don't have to think about this much. There are times where this can be useful, however, such as for closing open connections to remote services regardless of whether the request to it was successful or not.

## Promises and Async JavaScript

Admittedly, in our last post, we might have seemed a little negative about the design of JavaScript as a language. While it's almost certainly true that a lot of mistakes were made — and thanks to the ever-present need for backwards compatibility, many of them still exist today — arguably, there has been a lot of ground covered since then to make amends, and many aspects of the original design of JavaScript still hold up well today.

One of those areas that JavaScript is great at is asynchronous programming. JavaScript is an *event-driven* language, which is, in its simplest terms, the means to allow code to be executed by listening for *events* that can be triggered based on user interaction, or even messages from other programs. This is a great fit for a language like JavaScript that is mostly found embedded in a graphical environment, where you might feasibly want to execute code based on mouse clicks, or key presses. 

Thanks to JavaScript's Event Loop (a concept we'll cover in full in a later edition of [JavaScript Sorcery](https://blog.appsignal.com/category/javascript-sorcery.html)) and recent developments in the language, JavaScript lets you define points in your program where the flow of execution can be returned to the program in lieu of a value, allowing the rest of your program to run and the UI to update, and the value to the latter be filled later. We call these values `Promise`s.

`Promise`s themselves can contain exceptions, which when they are thrown, cause the `Promise` to become rejected. Once rejected, a `Promise` can execute a user-defined callback that we chain to it using `.catch`.

```js
// You can catch errors asynchronously by listening to Promises...
asyncActionThatReturnsAPromise().catch(error => appsignal.sendError(error));
```

Errors can also be caught in the `onRejected` handler, a second parameter to `.then` that takes a function.

```js
asyncActionThatReturnsAPromise().then(onFulfilled, onRejected):
```

The first argument to the `.catch` callback will normally be an `Error` object, but just like the `try`/ `catch` statements above, there is no explicit rule about what kind of value a `Promise` can be rejected with and thus passed to the `.catch` callback. It could technically be any value. We recommend that, when writing your own `Promise`s, you do yourself and any future developers using your code the courtesy to reject `Promise`s with `Error` objects.

Any `Promise`s that become rejected that don't have a callback bound to the `.catch` handler will instead fire a callback on the `window` object called `onunhandledrejection`.

```js
window.onunhandledrejection = function(e) {
  // error handling code here!
}
```

Recently, the ECMAScript standard was amended to add the `async`/`await` keywords. With these keywords, we can write async code that looks like synchronous code by using the `await` keyword within an `async` function to denote to the program that it should pause execution of the async function and wait for a value that a `Promise` is fulfilled with.

As we can use `async`/ `await` and async functions to write code that looks like it's synchronous even though it's not, then it's sensible to expect that we can also use the `try`/`catch` statement to handle exceptions within them, and in fact, we can!

```js
// ...or by using async/await
async function() {
  try {
    const result = await asyncActionThatReturnsAPromise();
  } catch (error) {
    appsignal.sendError(error);
    // handle the error
  }
}
```

## **C'est tout!**

That's all we have for this week!

Don't forget: our JavaScript integration was released recently and we'd love for you to [give it a try](https://docs.appsignal.com/front-end/) in your front-end applications and tell us what you think.

If you liked this post, [subscribe to our new JavaScript Sorcery list](https://blog.appsignal.com/javascript-sorcery) for a monthly deep dive into more magical JavaScript tips and tricks.