Error Handling

No matter how great we are at programming, sometimes our scripts have errors. They may occur because of our mistakes, an unexpected user input, an erroneous server response and for a thousand of other reasons. Usually, a script “dies” (immediately stops) in case of an error, printing it to console.

But there’s a syntax construct try..catch that allows to “catch” errors and, instead of dying, do something more reasonable.

The try..catch construct has two main blocks: try, and then catch:

try {

	// code...

} catch (err) {

	// error handling

}

So, an error inside the try {…} block does not kill the script: we have a chance to handle it in catch.

try..catch only works for runtime errors

For try..catch to work, the code must be runnable. In other words, it should be valid JavaScript. It won’t work if the code is syntactically wrong, for instance it has unmatched curly braces:

try {
	{{{{{{{{{{{{}}
} catch(e) {
	alert("The engine can't understand this code, it's invalid");
}

The JavaScript engine first reads the code, and then runs it. The errors that occur on the reading phrase are called “parse-time” errors and are unrecoverable (from inside that code). That’s because the engine can’t understand the code.

So, try..catch can only handle errors that occur in the valid code. Such errors are called “runtime errors” or, sometimes, “exceptions”.

try..catch works synchronously

If an exception happens in “scheduled” code, like in setTimeout, then try..catch won’t catch it:

try {
	setTimeout(function() {
		noSuchVariable; // script will die here
	}, 1000);
} catch (e) {
	alert( "won't work" );
}

That’s because try..catch actually wraps the setTimeout call that schedules the function. But the function itself is executed later, when the engine has already left the try..catch construct.

To catch an exception inside a scheduled function, try..catch must be inside that function:

setTimeout(function() {
	try {
		noSuchVariable; // try..catch handles the error!
	} catch (e) {
		alert( "error is caught here!" );
	}
}, 1000);

Error Object

When an error occurs, JavaScript generates an object containing the details about it. The object is then passed as an argument to catch:

try {
	// ...
} catch(err) { // <-- the "error object", could use another word instead of err
	// ...
}

For all built-in errors, the error object inside catch block has two main properties:

There are other non-standard properties available in most environments. One of most widely used and supported is:

For example -

try {
	lalala; // error, variable is not defined!
} catch(err) {
	alert(err.name); // ReferenceError
	alert(err.message); // lalala is not defined
	alert(err.stack); // ReferenceError: lalala is not defined at ...

	// Can also show an error as a whole
	// The error is converted to string as "name: message"
	alert(err); // ReferenceError: lalala is not defined
}

Example

As we already know, JavaScript supports the JSON.parse(str) method to read JSON-encoded values. Usually it’s used to decode data received over the network, from the server or another source. We receive it and call JSON.parse, like this:

let json = '{"name":"John", "age": 30}'; // data from the server

let user = JSON.parse(json); // convert the text representation to JS object

// now user is an object with properties from the string
alert( user.name ); // John
alert( user.age );  // 30

If json is malformed, JSON.parse generates an error, so the script “dies”. This way, if something’s wrong with the data, the visitor will never know that (unless they open the developer console). And people really don’t like when something “just dies” without any error message.

Let’s use try..catch to handle the error:

let json = "{ bad json }";

try {

	let user = JSON.parse(json); // <-- when an error occurs...
	alert( user.name ); // doesn't work

} catch (e) {
	// ...the execution jumps here
	alert( "Our apologies, the data has errors, we'll try to request it one more time." );
	alert( e.name );
	alert( e.message );
}

Throwing Error

What if json is syntactically correct, but doesn’t have a required name property? Like this:

let json = '{ "age": 30 }'; // incomplete data

try {

	let user = JSON.parse(json); // <-- no errors
	alert( user.name ); // no name!

} catch (e) {
	alert( "doesn't execute" );
}

Here JSON.parse runs normally, but the absence of name is actually an error for us. To unify error handling, we’ll use the throw operator.

The throw operator generates an error.

throw <error object>

Technically, we can use anything as an error object. That may be even a primitive, like a number or a string, but it’s better to use objects, preferably with name and message properties (to stay somewhat compatible with built-in errors).

JavaScript has many built-in constructors for standard errors: Error, SyntaxError, ReferenceError, TypeError and others. We can use them to create error objects as well.

let error = new Error(message);
// or
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...

For built-in errors (not for any objects, just for errors), the name property is exactly the name of the constructor. And message is taken from the argument.

let error = new Error("Things happen o_O");

alert(error.name); // Error
alert(error.message); // Things happen o_O

Let’s see what kind of error JSON.parse generates:

try {
	JSON.parse("{ bad json o_O }");
} catch(e) {
	alert(e.name); // SyntaxError
	alert(e.message); // Unexpected token o in JSON at position 0
}

As we can see, that’s a SyntaxError. And in our case, the absence of name could be treated as a syntax error also, assuming that users must have a name.

So let’s throw it:

let json = '{ "age": 30 }'; // incomplete data

try {

	let user = JSON.parse(json); // <-- no errors

	if (!user.name) {
		throw new SyntaxError("Incomplete data: no name"); // (*)
	}

	alert( user.name );

} catch(e) {
	alert( "JSON Error: " + e.message ); // JSON Error: Incomplete data: no name
}

In the line (*), the throw operator generates a SyntaxError with the given message, the same way as JavaScript would generate it itself. The execution of try immediately stops and the control flow jumps into catch.

Now catch became a single place for all error handling: both for JSON.parse and other cases.

Rethrowing

By default the catch block handles all kind of error. If try block generates error, it must fall in the catch block.

let json = '{ "age": 30 }'; // incomplete data

try {
  user = JSON.parse(json); // <-- forgot to put "let" before user

  // ...
} catch(err) {
  alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
  // (no JSON Error actually)
}

In the above exmple, it shows "JSON Error". Even if it is a ReferenceError. So this is wrong. To filter what kind of Error it is you can use name property of the error object.

let json = '{ "age": 30 }'; // incomplete data
try {

  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError("Incomplete data: no name");
  }

  blabla(); // unexpected error

  alert( user.name );

} catch(e) {

  if (e.name == "SyntaxError") {
    alert( "JSON Error: " + e.message );
  } else {
    throw e; // rethrow (*)
  }

}

The error throwing on line (*) from inside catch block “falls out” of try..catch and can be either caught by an outer try..catch construct (if it exists), or it kills the script.

So the catch block actually handles only errors that it knows how to deal with and “skips” all others. The example below demonstrates how such errors can be caught by one more level of try..catch:

function readData() {
  let json = '{ "age": 30 }';

  try {
    // ...
    blabla(); // error!
  } catch (e) {
    // ...
    if (e.name != 'SyntaxError') {
      throw e; // rethrow (don't know how to deal with it)
    }
  }
}

try {
  readData();
} catch (e) {
  alert( "External catch got: " + e ); // caught it!
}

Here readData only knows how to handle SyntaxError, while the outer try..catch knows how to handle everything.

try…catch…finally

The try..catch construct may have one more code clause: finally. If it exists, it runs in all cases:

try {
   ... try to execute the code ...
} catch(e) {
   ... handle errors ...
} finally {
   ... execute always ...
}

Try running this code:

try {
  alert( 'try' );
  if (confirm('Make an error?')) BAD_CODE();
} catch (e) {
  alert( 'catch' );
} finally {
  alert( 'finally' );
}

Variables are local inside try..catch..finally

Any variable declared with the keyword let can be accessible inside the block only. If it were decleared inside try then you cannot access it outside of try block. Similarly, the variable declared within catch block, can be accessible within the catch block only.

finally and return

The finally clause works for any exit from try..catch. That includes an explicit return.

In the example below, there’s a return in try. In this case, finally is executed just before the control returns to the outer code.

function func() {

  try {
    return 1;

  } catch (e) {
    /* ... */
  } finally {
    alert( 'finally' );
  }
}

alert( func() ); // first works alert from finally, and then this one

try..finally

The try..finally construct, without catch clause, is also useful. We apply it when we don’t want to handle errors right here, but want to be sure that processes that we started are finalized.

function func() {
  // start doing something that needs completion (like measurements)
  try {
    // ...
  } finally {
    // complete that thing even if all dies
  }
}

In the code above, an error inside try always falls out, because there’s no catch. But finally works before the execution flow jumps outside.

Global catch

Let’s imagine we’ve got a fatal error outside of try..catch, and the script died. Like a programming error or something else terrible. Is there a way to react on such occurrences? We may want to log the error, show something to the user (normally they don’t see error messages) etc.

There is none in the specification, but environments usually provide it, because it’s really useful. For instance, Node.JS has process.on(‘uncaughtException’) for that. And in the browser we can assign a function to special window.onerror property. It will run in case of an uncaught error.

window.onerror = function(message, url, line, col, error) {
  // ...
};
<script>
  window.onerror = function(message, url, line, col, error) {
    alert(`${message}\n At ${line}:${col} of ${url}`);
  };

  function readData() {
    badFunc(); // Whoops, something went wrong!
  }

  readData();
</script>

The role of the global handler window.onerror is usually not to recover the script execution – that’s probably impossible in case of programming errors, but to send the error message to developers.

There are also web-services that provide error-logging for such cases, like https://errorception.com or http://www.muscula.com.

They work like this: