Exceptions

  1. Check-first error handling
  2. The try..catch construct
    1. Obtaining the stack
  3. The full form of try..catch..finally
    1. try..catch..finally and return
  4. The throw statement
    1. A validator example
    2. Changes in the usage pattern
    3. Comparison
  5. Exception analysis and rethrow
  6. Summary

Understanding exception is important to object-oriented programming in general and JavaScript in particular.

Exceptions is a special, very powerful way to deal with errors.

Check-first error handling

Let’s take a bad code as an example. Like this:

nonexistant()

In the example above, a non-existing variable is accessed. What should a program do in this case?

The simple and very old concept is that the program dies. Let’s say we want to evade that sorrowful happening.

So, what to do in case when the variable may be undefined?

The simple way is to check it. Something like that:

if (window.func) {
  func()
}

There still may be an error if window.func is not a function. So we’ll need to check for it as well:

if (typeof(func) == 'function') { 
  func()
}

In the code above, typeof ensures that the variable exists and it is actually a function.

Hopefully we performed all necessary checks to ensure that running func is safe. But what if it isn’t? What if the func body has errors? Again, we want to handle the error, not let the script just die.

And here the try..catch construct kicks in.

The try..catch construct

The try..catch approaches the error handling from another side. Instead of “check if all ok, then do” approach, we try then catch errors.

A completely different way to handle errors which replaces the checking code.
The function example would look like:

try {
  func() 
} catch(e) {
  alert(e)
}

If an error occurs inside the try block, the control is passed to the catch(e) section.

It’s argument e is assigned to a special exception object which contains the information about what happened.

The variable e contains an instance of Error object (or it’s inheritant like TypeError, ReferenceError etc).

The error properties are little bit different between browsers, see Error in MDN and Error in MSDN for details.

But there are always basic attributes:

name
The error type, for browser-generated errors it matches error constructor function, like TypeError, ReferenceError etc.
message
The text message which tells more about the error.

Now let’s go further and add other statements into try section. In the example below, both name and message are printed.

try {
  var a = 5
  var res = func(a) 
  if (res > 0) doA()
  else doB()

} catch(e) {
  alert("name:" + e.name + "\nmessage:" + e.message)
}

Do you know one cool thing about the try..catch?

There are errors which can only be caught by try..catch, because you can’t detect a possible fail until you try.

That makes the try..catch construct extremely valuable and important.

Obtaining the stack

Browsers Firefox, Chrome, Opera provide additional stack property which allows to see the nested calls which led to the exception. Check it on the example below.

function f(a) { 
  g(a+1) 
}

function g(a) {
  notexists;
}

try { f(1) } catch(e) { alert(e.stack) }

Unfortunately, IE does not have this property even in IE9.

The full form of try..catch..finally

The full form of try..catch construct consists of three parts:

try {
   .. try statemenets ..
} catch(exception) {
   .. catch statements ..
} finally {
   .. finally statements ..
}

Works like this:

  1. The try statements are executed. If no errors occur, then the catch section is ignored.
  2. In case of an error, the exception variable is assigned to the error object and catch statements are executed.
  3. In both cases, after either successful try or catch, the finally code is executed.

The finally clause is used to perform actions which should be done in any way, like removing loading indicator in both cases: success or error. It is possible to omit catch if finally is provided:

// rarely used, but valid
try {
  ..
} finally {
  ..
}

try..catch..finally and return

The finally works in any case of leaving the try block.

In the following example, the return occurs from inside try, but finally still intercepts it and executes before the control is passed to the calling code.

function inc(a) {

  try {
    return a+1
  } catch(e) {
    // ..
  } finally {
    alert('done')
  }
}

alert( inc(1) )

The throw statement

Most errors can be split into two kinds:

Programmatic errors
The errors which occur because a developer did something wrong. An often example is a mistype.
Execution flow errors
An error which is a normal part of execution. A usual example is form validation. If the user entered something wrong, then it is normal to process the error and ask him to repeat.

It may be beneficial to use the try..catch with execution flow errors. To do it, we should be able to raise our own errors, which is done by throw.

The syntax is: throw e, where e is literally anything. No matter what you throw, it will be caught by the catch… Or make the program die if throw is done out of try section.

The example below demonstrates the idea of how throw works.

try {
  throw 5
} catch(e) {
  alert("Caught: "+e)
}

A validator example

For example, let’s write an age validator. It takes a variable and check it for valid age:

function validateAge(age) { // age is a text to check 
  if (age === '') return // no age is valid

  age = +age
  
  if (isNaN(age)) {
    throw { name: 'BadAge', message: 'Invalid age' }
  }
  if (age < 5 || age > 150) {
    throw { name: 'BadAge', message: 'Age out of range' }
  }
}

try {
  var age = prompt("Enter your age please?") 
 
  validateAge(age)

  alert("The age is accepted!")
} catch(e) {
  alert("Error: "+e.message)
}

Usually, it is better to inherit error objects from Error and support a manageable error hierarchy. So in the example above, there should be throw new BadAgeError("Invalid age").

By the way, not how the validator usage pattern gets changed.

Changes in the usage pattern

For example, we need to validate if a value is provided and that it’s a valid age. To do so, we implement validateAge and validateRequired.

We validate until first error.

The error-checking way:

Without exceptions, the validator could return either true or false. But that’s not enough, we need to know the error. So let it return the error object in case of error and undefined if all ok.

The usage pattern would be:

var value = input.value

// VALIDATE

var error = validateRequired(value)

if (!error) {
  error = validateAge(value)
}

if (!error) { 
  /* another validator... */ 
}

// FINISHED VALIDATING

if (error) {
  /* process error */
} else {
 /* success */
}

The try..catch way:
The validator returns nothing in this case. Actually, it just checks the value and throws an error if finds it.

var value = input.value

try {
  validateRequired(value)
  validateAge(value)
  // other validators in a row

  /* success */
} catch(e) {
  /* process error */
}

It is important and code-saving that we put many actions into the try block, not check them one-by-one. If all is fine, then all is fine. If something goes wrong, we’ll see what is it in the catch section.

Comparison

Here are advantages and disadvantages of using try..catch for error handling.

  • The try..catch way is usually cleaner and more reliable. It catches all errors.
  • There exist actions which you can’t check. So the try..catch is only the way to go. For example, testing some browser’s features is done by executing the code and watching for exceptions.
  • The try..catch construct itself takes several lines. The obvious overhead for simple stuff.

Exception analysis and rethrow

Sometimes, the code may produce different types of errors. In this case, the if is used to choose the correct action.

Here is a pseudocode, assuming all excepition object are instances of proper-named error objects:

try {
  // 1. do smth
} catch(e) {
  if (e instanceof ValidationError) {
    // 2.1 process e
  } else if (e instanceof PermissionError) {
    // 2.2 process e
  } else {
    // 3. we don't know how to deal with e
    throw e
  }
}

  1. The code in the try block is complex. It may throw errors, some of them we know how to process, like ValidationError. But other kinds of errors are possible.
  2. In the catch section we analyze the exception and process it if we are able to.
  3. Otherwise, the exception is rethrown. It is assumed that there is an outer try..catch block which knows how to deal with the error.

It is extremely important that an exception must be processed or rethrown, not left alone, unless you absolutely know what you’re doing.

Don't swallow the exception until you really sure

try {
  func()
} catch(e) {
  if (e instanceof KnownError) {
    // ...
  } 
}

In the snippet above, other exception types except KnownError are silently ignored.

Well, frankly, the antipattern of leaving exception unprocessed is more from the Java world. But anyway, leaving an exception object is dangerous.

Imagine, there is a mistype in the func in the example above. It will be very hard to debug, because all TypeError and ReferenceError exceptions are cought and ignored.

Summary

The try..catch..finally allows to join several statements in a single code-block try, and split error-handling into the separate catch block.

It allows to handle all errors, both JavaScript-generated and thrown manually.

Technically, JavaScript allows to throw any value, but it is recommended that all your errors inherit the basic Error object and form an hierarchy. In this case, instanceof works well.

For example, you can catch all e instanceof ValidationError including AgeValidationError, RequiredValidationError etc. Your own exceptions may have additional properties like extra or cause.

Tutorial

Donate

Donate to this project