support@90-10.dev

Harnessing the Power of Error Objects in JavaScript

Encountering errors is inevitable and JavaScript is no exception. Understanding and effectively utilising error objects can turn these seemingly annoying occurrences into valuable tools for debugging and error handling.

What are Error Objects?

Error objects are specialised objects that encapsulate information about an error that occurs during the execution of a script. They provide essential details about the error, such as a message describing the error, the type of error, and the location of the error in the source code. JavaScript has a built-in Error object, which serves as the base object for all error types.

Error objects have two primary properties: name and message. The name property refers to the error's type, and the message property contains a human-readable description of the error. In addition to these properties, the Error object also has a stack property, which returns a string representing the stack trace at the time the error was thrown.

function divide(a, b) {
    if (b === 0) {
      throw new Error("Division by zero");
    }
    return a / b;
}

function quotient(a, b) {
    return divide(a, b);
}

try {
    quotient(42, 0);
} catch (error) {
    console.error("Error message:", error.message);
     // prints: "Error message: Division by zero"
    
    console.error("Error stack trace:", error.stack);
    /* prints:
      Error stack trace: Error: Division by zero
        at divide (REPL24:3:11)
        at quotient (REPL28:2:10)
        at REPL35:2:3
    */
}

Creating Custom Error Objects

Extend the Error Object

To create a custom error object, you can extend the built-in Error object using the following pattern:

class CustomError extends Error {
  constructor(message) {
    super(message);
    this.name = 'CustomError';
  }
}

By extending the Error object, your custom error will inherit the properties and methods of the base Error object. This allows you to create more specific error types tailored to your application's needs.

Error.prototype

We can also extend the Error object by creating a custom constructor function. The constructor function should accept an error message as an argument and call the Error object's constructor with the message. This way, the new error object will have all the properties of the built-in Error object, along with any additional properties specified by the custom constructor function.

function CustomError(message) {
  this.name = 'CustomError';
  this.message = message || 'Default error message';
  this.stack = (new Error()).stack;
}

CustomError.prototype = Object.create(Error.prototype);
CustomError.prototype.constructor = CustomError;

Throwing and Catching Errors

To throw an error, you can use the 'throw' statement followed by an error object:

throw new Error('Something went wrong');

To catch and handle errors, JavaScript provides the 'try...catch' statement. The 'try' block contains the code that may generate an error, and the 'catch' block handles the error:

try {
  // Code that may throw an error
} catch (error) {
  console.error(error.message);
}

Built-in Error Objects

The language also provides several other built-in error objects derived from the base Error object, including:

ReferenceError

Triggered when attempting to use a variable that has not been defined or is not in the current scope:

function greet() {
  try {
    console.log("Hello " + name);
  } catch (error) {
    if (error instanceof ReferenceError) {
      console.error("Error:", error.message);
    } else {
      // any other error types
      console.error("Unexpected error:", error.message);
    }
  }
}

greet();  // prints: "Error: name is not defined"

TypeError

Triggered when attempting to perform an operation on a value of an incorrect type, such as calling a non-function as a function or accessing a property on a non-object:

const greeting = {
  hello: function () {
    return "Hello!";
  },
};

try {
    // prints: hello
    console.log(greeting["hello"]());
    // prints: TypeError: greeting.goodbye is not a function
    console.log(greeting["goodbye"]());
} catch (error) {
    if (error instanceof TypeError) {
        console.error("TypeError:", error.message);
    } else {
        // any other error types
        console.error("Unexpected error:", error.message);
    }
}

RangeError

Triggered when providing a value that is outside the allowed range for a particular operation or data structure:

function createArray(size) {
    try {
        return new Array(size);
    } catch (error) {
        if (error instanceof RangeError) {
            console.error("RangeError:", error.message);
        } else {
            // any other error types
            console.error("Unexpected error:", error.message);
        }
    }
}

// prints: RangeError: Invalid array length
createArray(-3);

SyntaxError

Triggered when there is an issue with the syntax of the code, typically during parsing or evaluation - a common occurence is when using eval():

function execute(code) {
    try {
        return eval(code);
    } catch (error) {
        if (error instanceof SyntaxError) {
            console.error("SyntaxError:", error.message);
        } else {
            // any other error types
            console.error("Unexpected error:", error.message);
        }
    }
}

// prints: "Hello, World!"
execute("console.log('Hello, World!');");

// prints: "SyntaxError: missing ) after argument list"
execute("console.log('Hello, World!';");

Remember that using eval() is generally discouraged in modern JavaScript programming, as it can lead to security and performance issues.

EvalError

This is a deprecated error object related to the global eval() function - it is not thrown by the current ECMAScript specification with other error types such as SyntaxError, ReferenceError, or TypeError are thrown when issues encountered.

URIError

Thrown when a malformed URI is provided to certain global functions, such as encodeURI(), decodeURI(), encodeURIComponent(), or decodeURIComponent():

function decode(encodedURI) {
    try {
        return decodeURIComponent(encodedURI);
    } catch (error) {
        if (error instanceof URIError) {
            console.error("URIError:", error.message);
            return;
        }
        // any other error types
        console.error("Unexpected error:", error.message);
    }
}

// prints: "://example.com/test"
decode("%3A%2F%2Fexample.com/test");

// prints: "URIError: URI malformed"
decode("%C0%AFexample.com/test%");

Best Practices for Using Error Objects

  • Always throw an instance of an error object instead of a plain string or number to ensure proper error handling.
  • Use appropriate built-in error types when throwing errors. E.g. throw a ReferenceError when encountering an issue with an undefined variable.
  • Create custom error objects for application-specific errors.
  • Utilize the 'stack' property of error objects for better debugging, especially in development environments.
  • Handle errors gracefully by providing fallback behavior or informative messages for users.

Take Away

Effectively utilising error objects in JavaScript can greatly enhance your error handling and debugging processes. By understanding the built-in error types, creating custom error objects, and implementing proper error handling techniques, you can develop more robust, resilient, and user-friendly applications. Embrace the power of error objects and turn those inevitable errors into opportunities for improvement.