javascript

JavaScript Errors: An Exceptional History

Adam Yeats

Adam Yeats on

JavaScript Errors: An Exceptional History

Hello again! It's a historic week here at AppSignal! 🎉

This week we released the first version of our new and improved JavaScript error monitoring. Now you can have your front end code, Ruby or Elixir back end code, your hosts, performance, everything monitored in one interface.

To celebrate the launch, in a two-part series of posts, we'll be taking a look at the history of Errors in JavaScript, including how to handle them in your code today. In this part, join us as we go through the origins and turbulent years of growth of JavaScript and see it grow into the language it is today.

A Comedy of Errors

In accordance with Murphy's Law, arguably ticket #1 on the backlog of the great Jira board of the universe, runtime errors have existed as a concept since the very first version of JavaScript, shipped as part of Netscape Navigator 2.0 in 1995, and later as part of the second iteration of Internet Explorer.

Here in 2019, it's difficult to conceive of a primordial version of JavaScript. Famously prototyped in 10 days, and publicly released mere months later, the early history of JavaScript exists as a flawed, but nonetheless impressive, monument to compromise and short-term corporate thinking; plans to implement Scheme as the embedded scripting language in Netscape Navigator gave way to desires to include a "glue language" to compliment Java, producing a hybrid of syntax vaguely resembling Java but with ideas cribbed from dynamically typed languages like Scheme, HyperTalk and Self in order to differentiate itself from the more "serious" and statically typed Java.

One Point Uh-Oh

JavaScript 1.0 shipped without some common language features. For instance, support for a list-like data structure (the Array object was added in version 1.1). Object and array literals ([] and {}) would not arrive until JavaScript 1.2. Some features changed rapidly — JavaScript 1.2 changed the == and != operators to make them no longer coerce types if the compared values were not of the same type. This would later be reversed with version 1.3 with the advent of the ECMA standardization, and the === and !== operators would fill the gap.

Perhaps most pertinent to the theme of this post, however, JavaScript shipped without the ability to handle runtime errors. If your script produced a runtime error, at the time, there was no mechanism to recover from this. A runtime error would "crash" the page, and present the user with an ugly, cryptic error message and no other option than to reload and hope that, fingers crossed, the script would evaluate correctly this time. Those of us using the web in those days don't get the warm-and-fuzzies when we remember the dreaded un-closable "Script error" dialog, but luckily those days are far behind us.

The wake of these, and many other, decisions of those ten days in May 1995 has stretched unusually far into the future. It can be argued that the perception of JavaScript as a folly, a mere toy language for those not competent enough to embrace "real" languages, is birthed from these early design flaws and Netscape's own marketing attempts to position JavaScript as a sidecar to Java, a reputation that has been hard for JavaScript to shake as its adoption and ubiquity grows.

Ultimately, however, extensions were made to the JavaScript language to address many of these initial hurdles, leading to the eventual standardization of JavaScript as ECMAScript in 1997, the formation of TC39, and the beginning of the tumultuous journey to the JavaScript of today.

An Exceptional History

The first version of the ECMA-262 standardization of JavaScript included a section on errors short enough to be almost memorizable. It talks very little about runtime errors, only hinting at "catchable [errors] in future versions". It would take until the 3rd Edition of ECMA-262 (colloquially known as ES3) for proper exception handling semantics to be ratified as part of the ECMAScript specification.

In the meantime, it would be Netscape who would take the initial steps and add two of the core pillars of JavaScript error handling.

window.onerror

Version 1.1 of JavaScript (shipped with Netscape Navigator 3.0), would be the first to have a mechanism for error handling of any kind, in the form of the window.onerror event. JavaScript was designed as an event-driven language from the very beginning.

In the Netscape Navigator 3.0 documentation, the window.onerror event is originally defined as firing when "the loading of a document or image causes an error".

In Navigator 3.0, handling an error would look like this:

javascript
function onError(msg, url, lineno) { // error handling code here! } window.onerror = onError;

This enabled JavaScript developers of the time to create something resembling a trace from the arguments provided to the event handler but did not offer any solutions with regards from recovering from an error.

try/catch

Basic exception handling constructs like try/catch and throw were not a part of language until JavaScript 1.4, a version of JavaScript that was only used for Netscape LiveWire, a server-side JavaScript implementation released in 1999, predating Node.js by a decade. They do not appear in the ECMAScript specification until version 3. This can be considered the second "true" version of the ECMAScript standard (ECMAScript 2 contained only editorial changes).

Although exception handling as a concept predates it by many years in both software and hardware, try/catch exception handling was a concept first introduced in software in C++ and later extended with the finally block in Java. A program can "try" executing a block of code that may throw an exception, and interrupt the normal flow of execution of our program and execute code defined in the catch block if it does.

A throw statement will pass the value to its right-hand side to the first argument of the catch block. This could be any value; a common pattern in JavaScript was to throw a string and handle the error conditionally. Over time, conventions and best practices evolved, and it would become more common to throw an Error object instead. No further statements beyond the throw statement will execute. Instead, the code inside the catch block will execute. This allows a developer to write logic to recover from runtime errors or log them somewhere for debugging at a later stage.

The finally block always executes, regardless of whether an exception was thrown or caught. If no catch block exists in the call stack when an exception is thrown, the script will simply terminate.

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

This change to JavaScript syntax would modify the semantics of the window.onerror handler somewhat. As many errors could now be handled at runtime, the window.onerror would begin to evolve into a funnel for unhandled exceptions.

The Error object

Another crucial ingredient of the JavaScript error handling picture is the Error object.

The Error page on MDN is a fascinating read in itself. The Error object is a compendium of inconsistency — a child of the badlands of the late 90's and oughts, an era in web development where new JavaScript features, rather than introduced and standardised on a yearly basis in a co-operative effort between vendors and developers like they are today, were created in a standoff of sorts between two major browser vendors vying for dominance of an industry in its infancy.

The roots of the Error object can be traced back to JScript 5, Microsoft's own reverse-engineered implementation of the ECMAScript standard and included in Internet Explorer. This marked the first time that instances of an Error object could be thrown when runtime errors occurred, and could also be inherited from to create custom exceptions.

javascript
function broken() { if (true) { throw new Error("I am an error"); } } try { broken(); } catch (e) { // e.name = "Error" // e.message = "I am an error" }

Early documentation from Netscape Navigator exists that implies the creation of your own custom exception object was expected, however Microsoft was somewhat ahead of the curve here with a more detailed exception object that could be thrown in place of a primitive value like a string.

A quirk of the early Microsoft Error object compared to the ECMA-standardised version is that the alternative arguments you could provide:

ts
new Error(num: number, description?: string)

The first argument was intended to be a value which, when combined with the & (bitwise And) operator to combine the number property with the hexadecimal number 0xFFFF, would return the actual error code.

The existence of the Error object was later given legitimacy as part of the ECMAScript 3 specification, however, it's implementations remain inconstant across browsers to this day. The Error object as specified in ECMA-262 has two intrinsic properties: the description of the error (message, description in earlier Microsoft implementations) and the name of the error (name).

However, browser vendors have since added their own extensions to the Error object, the most notable of which being the stack property, returning a string representation of which functions were called leading up to the exception, in what order, from which line and file, and with what arguments. Although not part of the ECMAScript standard, almost all modern browsers include an Error object with a stack property. Older browsers, such as IE9, do not include this functionality.

Backtracing to today

This brings us back to today. The AppSignal JavaScript library depends heavily on the stack property's presence to provide a backtrace in the UI — if it's not present, then there's no backtrace that we can display.

In some cases (namely inside the plugin for window events), we attempt to construct a partial stack trace if none is present from the best information available at the time, but in some situations, no stack trace will be available at all. Luckily for all of us, the percentage of browsers in use today that do not support the Error object's stack property is relatively marginal.

That’s it for now!

In our next post, we'll look at how errors are handled in JavaScript today, including methods to handle asynchronous code and how to best use the AppSignal JavaScript library to track them.

We're super excited to have our JavaScript error tracking out in the wild, so please give it a try and tell us what you think.

If you liked this post, subscribe to our new JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.

Become our next author!

Find out more

AppSignal monitors your apps

AppSignal provides insights for Ruby, Rails, Elixir, Phoenix, Node.js, Express and many other frameworks and libraries. We are located in beautiful Amsterdam. We love stroopwafels. If you do too, let us know. We might send you some!

Discover AppSignal
AppSignal monitors your apps