Dirk Harriman Banner Image

 

Notes Javascript - Error Handling


 

 

Throw Exception

The throw statement throws a user-defined exception. Execution of the current function will stop (the statements after throw won't be executed), and control will be passed to the first catch block in the call stack. If no catch block exists among caller functions, the program will terminate.
You can throw exceptions using the throw statement and handle them using the try...catch statements.

Exception Types

Just about any object can be thrown in JavaScript. Nevertheless, not all thrown objects are created equal. While it is common to throw numbers or strings as errors, it is frequently more effective to use one of the exception types specifically created for this purpose:


 

Throw Statement

Use the throw statement to throw an exception. A throw statement specifies the value to be thrown:

throw expression;

You may throw any expression, not just expressions of a specific type. The following code throws several exceptions of varying types:

throw "Error2"; // STRING TYPE throw 42; // NUMBER TYPE throw true; // BOOLEAN TYPE throw { toString() { return "I'm an object!"; }, }; throw new Error("Required"); // GENERATES AN ERROR OBJECT WITH THE MESSAGE OF Required


 

Error Object

Error objects are thrown when runtime errors occur. The Error object can also be used as a base object for user-defined exceptions.
Runtime errors result in new Error objects being created and thrown.

new Error() new Error(message) new Error(message, options) new Error(message, fileName) new Error(message, fileName, lineNumber) Error() Error(message) Error(message, options) Error(message, fileName) Error(message, fileName, lineNumber)

Error() can be called with or without new. Both create a new Error instance.

Error Object Parameters
Parameter Description
message A human-readable description of the error.
options An object that has the following properties:
 
cause - A value indicating the specific cause of the error, reflected in the cause property. When catching and re-throwing an error with a more-specific or useful error message, this property can be used to pass the original error.
fileName The path to the file that raised this error, reflected in the fileName property. Defaults to the name of the file containing the code that called the Error() constructor.
Note: this is optional and not a standard parameter.
lineNumber The line number within the file on which the error was raised, reflected in the lineNumber property. Defaults to the line number containing the Error() constructor invocation.
Note: this is optional and not a standard parameter.

Instance Properties

These properties are defined on Error.prototype and shared by all Error instances.

Property Description
Error.prototype.constructor The constructor function that created the instance object. For Error instances, the initial value is the Error constructor.
Error.prototype.name Represents the name for the type of error. For Error.prototype.name, the initial value is "Error". Subclasses like TypeError and SyntaxError provide their own name properties.
Error.prototype.stack A non-standard property for a stack trace.
These properties are own properties of each Error instance.
cause Error cause indicating the reason why the current error is thrown, usually another caught error. For user-created Error objects, this is the value provided as the cause property of the constructor's second argument.
message Error message. For user-created Error objects, this is the string provided as the constructor's first argument.

Throwing A Generic Error

Usually you create an Error object with the intention of raising it using the throw keyword. You can handle the error using the try...catch construct:

try { throw new Error("Whoops!"); } catch (e) { console.error(`${e.name}: ${e.message}`); }

function runScript3() { let resultDiv = document.getElementById("results3"); try { throw new Error("Whoops!"); } catch (e) { resultDiv.innerHTML = "e.name: "+ e.name +"<br/>e.message: "+ e.message; } }

Run Script
 

 

Handling A Specific Error Type

You can choose to handle only specific error types by testing the error type with the error's constructor property or, if you're writing for modern JavaScript engines, instanceof keyword:

try { foo.bar(); } catch (e) { if (e instanceof EvalError) { console.error(`${e.name}: ${e.message}`); } else if (e instanceof RangeError) { console.error(`${e.name}: ${e.message}`); } // etc. else { // If none of our cases matched leave the Error unhandled throw e; } }


 

Differentiate Between Similar Errors

Sometimes a block of code can fail for reasons that require different handling, but which throw very similar errors (i.e. with the same type and message).
If you don't have control over the original errors that are thrown, one option is to catch them and throw new Error objects that have more specific messages. The original error should be passed to the new Error in the constructor's options parameter as its cause property. This ensures that the original error and stack trace are available to higher-level try/catch blocks.
The example below shows this for two methods that would otherwise fail with similar errors (doFailSomeWay() and doFailAnotherWay()):

function doWork() { try { doFailSomeWay(); } catch (err) { throw new Error("Failed in some way", { cause: err }); } try { doFailAnotherWay(); } catch (err) { throw new Error("Failed in another way", { cause: err }); } } try { doWork(); } catch (err) { switch (err.message) { case "Failed in some way": handleFailSomeWay(err.cause); break; case "Failed in another way": handleFailAnotherWay(err.cause); break; } }

If you are making a library, you should prefer to use error cause to discriminate between different errors emitted, rather than asking your consumers to parse the error message.

Custom error types can also use the cause property, provided the subclasses' constructor passes the options parameter when calling super(). The Error() base class constructor will read options.cause and define the cause property on the new error instance.

class MyError extends Error { constructor(message, options) { // Need to pass `options` as the second parameter to install the "cause" property. super(message, options); } } console.log(new MyError("test", { cause: new Error("cause") }).cause); // Error: cause


 

Custom Error Types

You might want to define your own error types deriving from Error to be able to throw new MyError() and use instanceof MyError to check the kind of error in the exception handler. This results in cleaner and more consistent error handling code.

Some browsers include the CustomError constructor in the stack trace when using ES2015 classes.

class CustomError extends Error { constructor(foo = "bar", ...params) { // PASS REMAINING ARGUMENTS (INCLUDING VENDOR SPECIFIC ONES) TO PARENT CONSTRUCTOR super(...params); // MAINTAINS PROPER STACK TRACE FOR WHERE OUR ERROR WAS THROWN (ONLY AVAILABLE ON V8) if (Error.captureStackTrace) { Error.captureStackTrace(this, CustomError); } this.name = "CustomError"; // CUSTOM DEBUGGING INFORMATION this.foo = foo; this.date = new Date(); } } try { throw new CustomError("baz", "bazMessage"); } catch (e) { console.error(e.name); // CustomError console.error(e.foo); // baz console.error(e.message); // bazMessage console.error(e.stack); // stacktrace }

Run Script
 

 

 

See below for standard built-in error types.

ECMAScript Exception
Type Description
EvalError Creates an instance representing an error that occurs regarding the global function eval().
RangeError Creates an instance representing an error that occurs when a numeric variable or parameter is outside its valid range.
ReferenceError Creates an instance representing an error that occurs when de-referencing an invalid reference.
SyntaxError Creates an instance representing a syntax error.
TypeError Creates an instance representing an error that occurs when a variable or parameter is not of a valid type.
URIError Creates an instance representing an error that occurs when encodeURI() or decodeURI() are passed invalid parameters.
AggregateError Creates an instance representing several errors wrapped in a single error when multiple errors need to be reported by an operation, for example by Promise.any().

 

DOMException

The DOMException interface represents an abnormal event (called an exception) that occurs as a result of calling a method or accessing a property of a web API. This is how error conditions are described in web APIs.

Each exception has a name, which is a short "PascalCase"-style string identifying the error or abnormal condition.

DOMException is a Serializable object, so it can be cloned with structuredClone() or copied between Workers using postMessage().

new DOMException() new DOMException(message) new DOMException(message, name)

Error Names

Common error names are listed here. Some APIs define their own sets of names, so this is not necessarily a complete list.

The following deprecated historical errors don't have an error name but instead have only a legacy constant code value and a legacy constant name:

DOMException Exception
Type Description
IndexSizeError The index is not in the allowed range. For example, this can be thrown by the Range object. (Legacy code value: 1 and legacy constant name: INDEX_SIZE_ERR)
HierarchyRequestError The node tree hierarchy is not correct. (Legacy code value: 3 and legacy constant name: HIERARCHY_REQUEST_ERR)
WrongDocumentError The object is in the wrong Document. (Legacy code value: 4 and legacy constant name: WRONG_DOCUMENT_ERR)
InvalidCharacterError The string contains invalid characters. (Legacy code value: 5 and legacy constant name: INVALID_CHARACTER_ERR)
NoModificationAllowedError The object cannot be modified. (Legacy code value: 7 and legacy constant name: NO_MODIFICATION_ALLOWED_ERR)
NotFoundError The object cannot be found here. (Legacy code value: 8 and legacy constant name: NOT_FOUND_ERR)
NotSupportedError The operation is not supported. (Legacy code value: 9 and legacy constant name: NOT_SUPPORTED_ERR)
InvalidStateError The object is in an invalid state. (Legacy code value: 11 and legacy constant name: INVALID_STATE_ERR)
InUseAttributeError The attribute is in use. (Legacy code value: 10 and legacy constant name: INUSE_ATTRIBUTE_ERR)
SyntaxError The string did not match the expected pattern. (Legacy code value: 12 and legacy constant name: SYNTAX_ERR)
InvalidModificationError The object cannot be modified in this way. (Legacy code value: 13 and legacy constant name: INVALID_MODIFICATION_ERR)
NamespaceError The operation is not allowed by Namespaces in XML. (Legacy code value: 14 and legacy constant name: NAMESPACE_ERR)
InvalidAccessError The object does not support the operation or argument. (Legacy code value: 15 and legacy constant name: INVALID_ACCESS_ERR)
SecurityError The operation is insecure. (Legacy code value: 18 and legacy constant name: SECURITY_ERR)
TimeoutError The operation timed out. (Legacy code value: 23 and legacy constant name: TIMEOUT_ERR)
NotAllowedError The request is not allowed by the user agent or the platform in the current context, possibly because the user denied permission (No legacy code value and constant name).

 

Try Catch

The try...catch statement marks a block of statements to try, and specifies one or more responses should an exception be thrown. If an exception is thrown, the try...catch statement catches it.

The try...catch statement consists of a try block, which contains one or more statements, and a catch block, containing statements that specify what to do if an exception is thrown in the try block.

In other words, you want the try block to succeed—but if it does not, you want control to pass to the catch block. If any statement within the try block (or in a function called from within the try block) throws an exception, control immediately shifts to the catch block. If no exception is thrown in the try block, the catch block is skipped. The finally block executes after the try and catch blocks execute but before the statements following the try...catch statement.

The following example uses a try...catch statement. The example calls a function that retrieves a month name from an array based on the value passed to the function. If the value does not correspond to a month number (1 - 12), an exception is thrown with the value 'InvalidMonthNo' and the statements in the catch block set the monthName variable to 'unknown'.

function getMonthName(mo) { mo--; // Adjust month number for array index (so that 0 = Jan, 11 = Dec) const months = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; if (months[mo]) { return months[mo]; } else { throw new Error("InvalidMonthNo"); // throw keyword is used here } } try { // statements to try monthName = getMonthName(myMonth); // function could throw exception } catch (e) { monthName = "unknown"; logMyErrors(e); // pass exception object to error handler (i.e. your own function) }


 

The Catch Block

The catch block specifies an identifier (catchID in the preceding syntax) that holds the value specified by the throw statement. You can use this identifier to get information about the exception that was thrown.

JavaScript creates this identifier when the catch block is entered. The identifier lasts only for the duration of the catch block. Once the catch block finishes executing, the identifier no longer exists.

For example, the following code throws an exception. When the exception occurs, control transfers to the catch block.

try { throw "myException"; // generates an exception } catch (err) { // statements to handle any exceptions logMyErrors(err); // pass exception object to error handler }

Note: When logging errors to the console inside a catch block, using console.error() rather than console.log() is advised for debugging. It formats the message as an error, and adds it to the list of error messages generated by the page.


 

The Finally Block

The finally block contains statements to be executed after the try and catch blocks execute. Additionally, the finally block executes before the code that follows the try…catch…finally statement.

It is also important to note that the finally block will execute whether or not an exception is thrown. If an exception is thrown, however, the statements in the finally block execute even if no catch block handles the exception that was thrown.

You can use the finally block to make your script fail gracefully when an exception occurs. For example, you may need to release a resource that your script has tied up.

The following example opens a file and then executes statements that use the file. (Server-side JavaScript allows you to access files.) If an exception is thrown while the file is open, the finally block closes the file before the script fails. Using finally here ensures that the file is never left open, even if an error occurs.

openMyFile(); try { writeMyFile(theData); // THIS MAY THROW AN ERROR } catch (e) { handleError(e); // IF AN ERROR OCCURRED, HANDLE IT } finally { closeMyFile(); // ALWAYS CLOSE THE RESOURCE }

If the finally block returns a value, this value becomes the return value of the entire try...catch...finally production, regardless of any return statements in the try and catch blocks:

function f() { try { console.log(0); throw "bogus"; } catch (e) { console.log(1); // THIS RETURN STATEMENT IS SUSPENDED // UNTIL FINALLY BLOCK HAS COMPLETED return true; console.log(2); // NOT REACHABLE } finally { console.log(3); return false; // OVERWRITES THE PREVIOUS "RETURN" console.log(4); // NOT REACHABLE } // "return false" IS EXECUTED NOW console.log(5); // NOT REACHABLE } console.log(f()); // 0, 1, 3, false

Overwriting of return values by the finally block also applies to exceptions thrown or re-thrown inside of the catch block:


 

Nesting Try - Catch Statements

You can nest one or more try...catch statements.

If an inner try block does not have a corresponding catch block:

1. It must contain a finally block, and
2. The enclosing try...catch statement's catch block is checked for a match.

 

Utilizing Error Objects

Depending on the type of error, you may be able to use the name and message properties to get a more refined message.

The name property provides the general class of Error (such as DOMException or Error), while message generally provides a more succinct message than one would get by converting the error object to a string.

If you are throwing your own exceptions, in order to take advantage of these properties (such as if your catch block doesn't discriminate between your own exceptions and system ones), you can use the Error constructor.

For example:

function doSomethingErrorProne() { if (ourCodeMakesAMistake()) { throw new Error("The message"); } else { doSomethingToGetAJavaScriptError(); } } try { doSomethingErrorProne(); } catch (e) { // Now, we actually use `console.error()` console.error(e.name); // 'Error' console.error(e.message); // 'The message', or a JavaScript error message }


 

Unconditional Catch Block

When a catch block is used, the catch block is executed when any exception is thrown from within the try block. For example, when the exception occurs in the following code, control transfers to the catch block.

function runScript1() { let resultDiv = document.getElementById("results1"); let resultStr = ""; try { throw "myException"; // GENERATES AN EXCEPTION } catch (e) { // STATEMENTS TO HANDLE ANY EXCEPTIONS resultStr = e; } resultDiv.innerHTML = "Error: "+ resultStr; }

Run Script
 

 

 

Conditional Catch Block

You can create "Conditional catch blocks" by combining try...catch blocks with if...else if...else structures, like this:

try { myroutine(); // may throw three types of exceptions } catch (e) { if (e instanceof TypeError) { // statements to handle TypeError exceptions } else if (e instanceof RangeError) { // statements to handle RangeError exceptions } else if (e instanceof EvalError) { // statements to handle EvalError exceptions } else { // statements to handle any unspecified exceptions logMyErrors(e); // pass exception object to error handler } }

A common use case for this is to only catch (and silence) a small subset of expected errors, and then re-throw the error in other cases:

try { myRoutine(); } catch (e) { if (e instanceof RangeError) { // statements to handle this very common expected error } else { throw e; // re-throw the error unchanged } }

function runScript2() { let resultDiv = document.getElementById("results2"); let resultStr = ""; try { // INTENTIONALLY CREATE A RANGE ERROR EXCEPTION TO BE THROWN resultStr = getVal2(12); } catch (e) { if (e instanceof TypeError) { // statements to handle TypeError exceptions resultStr += "TypeError Thrown: "+ e; } else if (e instanceof RangeError) { // statements to handle RangeError exceptions resultStr += "RangeError Thrown: "+ e; } else if (e instanceof EvalError) { // statements to handle EvalError exceptions resultStr += "EvalError Thrown: "+ e; } else { // statements to handle any unspecified exceptions resultStr += "Error-Misc Thrown: "+ e; } } resultDiv.innerHTML = resultStr; } function getVal2(n) { let myVal = 2; if (n >= 10) { throw new RangeError("The argument must be between 0 and 9."); } else { myVal = myVal * n; } return myVal; }

Run Script
 

 

 

Quick Reference

Errors can occur for a number of reasons, some can be anticipated and some cannot. For example, many user input errors can be anticipated, but many errors can occur that have nothing to do with the user per se. Thing such as an outside resource failing, a connection error or invalid data not generated by the user.

// TRY...CATCH try { } catch { } // TRY...FINALLY try { } finally { } // TRY...CATCH...FINALLY try { } catch { } finally { }

try { // CODE EXECUTED THAT MIGHT CAUSE AN ERROR } catch(exceptionVar) { // CODE THAT IS EXECUTED IF AN EXCEPTION HAS // OCCURED IN THE TRY-BLOCK. ERROR CAN BE HANDLED DIRECTLY // OR CAN BE SENT TO AN ERROR HANDLING FUNCTION } finally { // CODE THAT IS EXECUTED BEFORE CONTROL FLOW EXITS // THE TRY...CATCH...FINALLY CONSTRUCT. // THESE STATEMENTS EXECUTE REGARDLESS OF WHETHER AN // EXCEPTION WAS THROWN OR CAUGHT. }

exceptionVar

An optional identifier or pattern to hold the caught exception for the associated catch block. If the catch block does not use the exception's value, you can omit the exceptionVar and its surrounding parentheses.
When an exception is thrown in the try block, exceptionVar (i.e., the e in catch (e)) holds the exception value. You can use this variable to get information about the exception that was thrown. This variable is only available in the catch block's scope.
It need not be a single identifier. You can use a destructuring pattern to assign multiple identifiers at once.

try { throw new TypeError("oops"); } catch ({ name, message }) { // name = "TypeError" // message = "oops" }

TypeError Object

The TypeError object is a subclass of Error. The TypeError object represents an error when an operation could not be performed, typically (but not exclusively) when a value is not of the expected type.
A TypeError may be thrown when:

try { throw new TypeError("oops"); } catch (e) { // e instanceof TypeError.......true // e.message...................."oops". null if not set // e.name......................."TypeError" // e.stack......................Stack of error }

Developer Defined Errors

Many if not most errors thrown will be done by Javascript. In the case of anticipated errors, the developer can create custom errors and error handlers.

throw "MyDefinedError1"; // GENERATES AN EXCEPTION WITH A STRING VALUE throw 56; // GENERATES AN EXCEPTION WITH AN INTEGER VALUE throw true; // GENERATES AN EXCEPTION WITH A BOOLEAN VALUE throw new Error("Required"); // GENERATES AN ERROR OBJECT WITH A MESSAGE OF "Required"

function UserException(message) { this.message = message; this.name = "UserException"; } function someFunction(InVar) { // ... if (/* SOME VALIDITY TEST */) { return someValue; } else { throw new UserException("SomeMessage"); } } function myErrorHandler(message, name) { // ... } try { funcVal = someFunction(InvalidVal); } catch(e) { myErrorHandler(e.message, e.name) }

/* Creates a ZipCode object: Accepted formats for a zip code are: 12345 12345-6789 123456789 12345 6789 */ // ZIPCODE CLASS class ZipCode { static pattern = /[0-9]{5}([- ]?[0-9]{4})?/; // CLASS CONSTRUCTOR constructor(zip) { zip = String(zip); const match = zip.match(ZipCode.pattern); if (!match) { throw new ZipCodeFormatException(zip); } // ZIP CODE VALUE WILL BE THE FIRST MATCH IN THE STRING this.value = match[0]; } // VALUE OF METHOD valueOf() { return this.value; } // TO STRING METHOD toString() { return this.value; } } // ZIP CODE FORMAT EXCEPTION class ZipCodeFormatException extends Error { constructor(zip) { super(`${zip} does not conform to the expected format for a zip code`); } } // CONSTANT ERROR TYPE VALUES const ZIPCODE_INVALID = -1; const ZIPCODE_UNKNOWN_ERROR = -2; function verifyZipCode(z) { try { // THE FOLLOWING CALLS THE ZipCode CONSTRUCTOR // IF THERE IS AN ERROR IN THE FORMAT, AN EXCEPTION WILL BE THROWN z = new ZipCode(z); } catch (e) { const isInvalidCode = e instanceof ZipCodeFormatException; return isInvalidCode ? ZIPCODE_INVALID : ZIPCODE_UNKNOWN_ERROR; } return z; } a = verifyZipCode(95060); // 95060 b = verifyZipCode(9560); // -1 c = verifyZipCode("a"); // -1 d = verifyZipCode("95060"); // 95060 e = verifyZipCode("95060 1234"); // 95060 1234

  Run Script