Open CheatSheet Back To List Next Tutorial

Errors

Intro

  • This tutorial covers JavaScript Errors.

  • It is recommended that you follow along with each section below. Create a folder called errors. Paste or type the code into your own JavaScript and module files. 

  • The example code for the entire tutorial series is at github.com/LearnByCheating/javascript-tutorial. There is a separate folder called errors that holds the files from this tutorial. The Readme file has instructions on how to run the code in the Node.js environment.


1. Errors Overview

  • Errors are violations of the language rules detected by the JavaScript engine at runtime. 
  • If an error is detected JavaScript automatically creates an Error object and and throws it, which crashes the program.
  • Below is an error example. The statement tries to access a variable named x that has not been declared:
let doubled = x * 2; 
    // crashes the program and logs: Uncaught ReferenceError ReferenceError: x is not defined


Try...Catch statement

  • To avoid crashing the program when an error occurs you can put code that is at risk of throwing an error inside a try...catch statement.
  • Format: 
try {
  /* statements to execute that may throw errors or exceptions */
} catch (err) {
  /* statements to handle any errors or exceptions */
}

  • If an error is detected in the try block, An error object is automatically created and thrown.
  • The thrown error object is passed to the catch block and placed in the parameter. You can give it any name so we are calling it err
  • Statements in the catch block handle the error.

  • Example:
try {
  let doubled = x * 2;
} catch (err) {
  console.error(err.toString()); // logs: ReferenceError: x is not defined
}

  • Since there is no x variable, the error gets thrown and is passed to the catch block in the err parameter.
  • In the catch block we log the error to the console.
  • Console.error does not have any special meaning. The console program will just display it differently. In the Debug Console, console log is printed in blue letters, while console error is printed in red.


Error Instance properties and methods
  • The error object has a few properties and methods:
    • Name: the name of the error's constructor function (e.g., ReferenceError)
    • Message: A message providing some information about the error.
    • ToString(): Returns a string with the error name, a colon, then the message.
    • Stack: The stack property is non-standard. It is available as part of the JavaScript V8 engine used by Node.js, Chrome and Edge, but it is not part of the official ECMAScript standard.
      • On the first line is the error name and message. 
      • Then it displays the stack trace starting with the file path, line number and character position where the error occurred. 

  • Below is the try...catch example using each of the Error instance properties and methods:
try {
  let doubled = x * 2;
} catch (err) {
console.error('E1:', err); // returns the error object console.error('E2:', err.name); // returns 'ReferenceError' console.error('E3:', err.message); // returns 'x is not defined' console.error('E4:', err.toString()); // returns `${err.name}: ${err.message}` console.error('E5:', err.stack); // returns 'ReferenceError: x is not defined at file:line'
}


VS Code Error Breakpoints
  • VS Code's Run and Debug sidebar lets you automatically break on errors. 
  • Open the Run and Debug Sidebar, then click on the Breakpoints panel if it is not already open. 
  • Check the Caught and Uncaught Exceptions boxes. Then when you run the Debugger it will stop on errors and display the error object. 
  • This feature is useful for debugging in development.


Standard Built-in Error Types

  • Error is a JavaScript standard built-in object on the global scope.
  • It is a constructor function, so you can call it with the new operator to create Error objects. We will cover instantiating our own error objects later in this tutorial.
let view = typeof Error; // returns 'function'
    const err = new Error('message goes here'); // instantiates a new Error object
    view = typeof err; // returns 'object'
    

  • The Error.prototype object holds the instance properties and methods we used when logging the error: 
Object.getOwnPropertyNames(Error.prototype); // Returns: constructor, name, message, toString.
    // There is also a non-standard property called stack.

  • Besides the main Error constructor, JavaScript has constructors for specific types of errors. The most commonly used include: SyntaxError, ReferenceError, TypeError, and RangeError. These all have their own constructor functions that inherit from Error. 

  • At runtime, when the JavaScript engine parses the code and checks for errors, if one is detected it instantiates an error object from the appropriate constructor and then throws the error.

  • Let's go through the common error types.


2. Syntax errors

  • SyntaxErrors are thrown when JavaScript tries to interpret code with invalid syntax. They are detected  when the script is compiled by the JavaScript engine before the code is executed. As such, Syntax errors cannot be caught in try catch blocks. They will crash your program so syntax errors must be found and corrected in development.

  • Below is a syntax error example. We are trying to declare a variable with the let keyword, but let is misspelled.
lett y = 9; // throws SyntaxError: Unexpected identifier

  • Below is another syntax error. The function declaration is missing the closing parentheses around the argument. 
function dbl(num { return num * 2; } // throws SyntaxError: Unexpected token '{'

  • If you are following along with this tutorial, comment out these syntax errors before moving on to the next section, otherwise the program will keep crashing.


3. Reference Errors

  • ReferenceErrors are thrown when a non-existent variable is referenced.
  • Below is an example we used earlier. We try to reference variable x. But it was never declared, so it throws an error which we catch in the catch block and log it.
try {
  let doubled = x * 2;
} catch (err) {
  console.error(err.toString()); // logs: ReferenceError: x is not defined
}

  • Calling a non-existent function will also throw a reference error:
try {
  nofunc();
} catch (err) {
  console.error(err.toString()); // logs: ReferenceError: nofunc is not defined
}


4. TypeErrors

  • Type Errors are thrown when an operation could not be performed, typically (but not exclusively) when a value is not of the expected type.
  • For example if you declare a const variable then try to reassign the value, it will throw a type error. 
const x = 7; 
x = 9; // throws TypeError: Assignment to constant variable at file:line

  • Or if you try to call a variable as if it was a function when it's not, it will throw a type error.
x(); // throws TypeError: x is not a function at file:line


5. Range Error

  • A RangeError is thrown when a value is not in the set or range of allowed values.
  • Below is an example of an infinite recursive function. It keeps calling itself and adding one to the parameter. 
try {
  function addOne(num) {
    addOne(++num);
  }
  addOne(0);
} catch (err) {
  console.error(err.toString()); // RangeError: Maximum call stack size exceeded.
}

  • Since this would go on forever, the JavaScript engine puts a limit on the call stack size and throws a RangeError once the limit is exceeded.
  • The error gets caught, and the error string is logged.


6. User-defined Exceptions: Throw Statement and Error object

  • Now let's talk about user-defined exceptions.
  • Errors are clear violations of language rules like bad syntax, referencing a variable that doesn't exist, trying to reassign a constant variable, etc.
  • Exceptions are violations of conditions. 

  • In general you would handle violations of conditions without creating and throwing an error. Below is an example.

No Error object or throw statement example:
  • Let's create a function that checks for a condition. 
  • The below function checks if the argument is a finite number using the Number.isFinite method. 
    • If it is, it returns "ok". 
    • Generally you would handle your unmet conditions without throwing an exception. So if it is not a finite number we just return the string "Must be a finite number.". 
function checkNum1(num) {
  if (!Number.isFinite(num)) return 'Must be a finite number.';
  return 'ok';
}
let res = checkNum1(10); // Returns "ok"
res = checkNum1(Infinity); // returns Must be a finite number.

Handle an exception like an error:
  • There may be situations where it makes sense to handle a violation of a condition as an error. You would test for the condition violation, and if present, instantiate and throw an error object. 

  • To demonstrate how error objects and the throw statement work together, we will start by creating an Error object but not throwing it, then throw an error that is not an error object.

Error object but no throw statement example:

  • You can create an error object without throwing it. 
  • If the argument pass into our function is not a finite number then we create an error object but there is no throw statement so we don't throw it. As such, it gets treated like any regular return value without crashing the program or needing to be caught. 
function checkNum2(num) {
  if (!Number.isFinite(num)) return new Error('Must be a finite number.');
  return 'ok';
}
res = checkNum2(Infinity) res.toString(); // returns Error: Must be a finite number.

Throw statement but no Error object example:
  • In the next example, if the argument is not a finite number we will throw an error, but we are not creating an Error object. We just throw a message string.
  • Regardless of whether we create an Error object or not, if we use the throw statement then it will crash the program if we do not catch it. 
  • So we call the function from a try...catch statement and catch the error. Then log the error string.
function checkNum3(num) {
  if (!Number.isFinite(num)) throw 'Must be a finite number.';
  return 'ok';
}
try {
  res = checkNum3(Infinity);
} catch (err) {
  console.error(err); // logs: "Must be a finite number."
}

Error object and throw statement example:
  • Now let's put those two together. If the argument is not a finite number then it violates our condition so we instantiate and throw an Error object.
  • Then we catch the error and log it to the console.
function checkNum4(num) {
  if (!Number.isFinite(num)) {
    throw new Error('Must be a finite number.');
  }
  return 'ok';
}
try {
  res = checkNum4(Infinity);
} catch (err) {
  console.error(err.toString()); // logs: "Error: Must be a finite number."
}


7. Stack Trace

  • For the last topic we will cover how to use the Error stack property to pinpoint where the error occurred.
  • Note that the stack property is non-standard. It is available in the V8 JavaScript engine used by Node.js, Chrome and Edge.

  • Create a module named calculator.mjs with two functions named double and triple, and export them at the bottom.
calculator.mjs
const double = (num) => {
      if (!Number.isFinite(num)) {
        throw new Error('Must be a finite number.');
      }
      return num * 2;
    };
    
    const triple = (num) => {
      return numVar * 3;
    };
    
    export { double, triple };

  • Note, the triple function contains a ReferenceError. We are trying to multiply a variable called numVar by 3. But numVar is not defined anywhere. We probably meant to use the parameter num but messed up.

  • Create another module file called app.mjs. At the top of the file import the two functions:
app.mjs
import { double, triple } from './calculator.mjs';

  • Call the double function in a try...catch statement. 
let res;
try {
  res = double(Infinity);
} catch (err) {
  console.error(err.stack);
}

  • In the calculator module, the double function checks that the parameter is a finite number:
    • if (!Number.isFinite(num)) {...}
  • If not, it throws an Error object: 
    • throw new Error('Must be a finite number.');

  • Because the number in our function call is not finite we get a thrown error. We catch it and log the stack property console.error(err.stack):

Error: Must be a finite number.
    at double (file:///path/to/javascript-tutorial/errors/calculator.mjs:3:11)
    at file:///path/to/javascript-tutorial/errors/app.mjs:7:9
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

  • On the first line it shows the error name and message. That tells you what the problem is, but not where it is.
  • The next lines log the call stack, which we can use to trace the location of the error.
  • The first line pinpoints where the error originated. 
    • It starts with the construct that caused the error, in this case in the double function.
    • Then the file path.
    • The line number, and the character position.
  • The second line is the location in your code where the erroneous code was called. In this case it was in the app.js file on line 7, character position 9.
  • The rest of the lines below it are internal Node.js modules which you can ignore.

  • Let's look at another example.
  • The calculator module exports a function called triple that contains a reference error. It calls a variable named numVar that was never declared:
    • const triple = (num) => { return numVar * 3 };

  • In the app.js file let's call the triple function in a try...catch statement.
try {
  res = triple(10);
} catch (err) {
  console.error(err.stack);
}

  • When we call the function an error gets thrown. 
  • It gets caught, and we log the error stack property to the console:
ReferenceError: numVar is not defined
    at triple (file:///path/to/javascript-tutorial/errors/calculator.mjs:9:3)
    at file:///path/to/javascript-tutorial/errors/app.mjs:15:9
    at ModuleJob.run (node:internal/modules/esm/module_job:194:25)

  • The first line tells us this is a ReferenceError, and numVar is not defined.
  • The stack trace tells us that the error occurs in the triple function defined in the calculator.js file on line 9, starting at character position 3.
  • And the triple function was called in the app.js file on line 15, character position 9.
  • And we can ignore the internal Node.js stack trace.

  • And that concludes this tutorial on JavaScript Errors.


Conclusion

The topics in this tutorial correspond with the JavaScript CheatSheet Errors category. Make sure you understand each topic so you can refer back to the CheatSheet when working on your own projects.

If you have the CheatSheet desktop app and downloaded the JavaScript CheatSheet, then go through the flashcards for this category to the point where you can answer them all in order and shuffled.
Open CheatSheet Back To List Next Tutorial