Node.js Error Handling

Node.js – Error Handling: Errors are part of every programming language, and Node.js Environment is not an exception. There are many predefined errors in Node.js, including errors like SyntaxError, RangeError, and many more. The official documentation of Node.js has provided a detailed description of the Errors. However, the point to be noted is that this documentation also refers to the Exceptions. So, let just not be confused with Errors over Exceptions.

Exceptions are the values thrown as the Output of the Invalid Operations or as the throw statements' targets. Therefore, all exceptions thrown by the JavaScript runtime or Node.js will be the instances of Errors. Thus, we will be handling errors as exceptions in Node.js.

Understanding the Types of Errors in Node.js

Firstly, it is essential to have a clear understanding of errors in Node.js. Generally, the errors in Node.js are classified into two different categories. The First one is Operational errors, and the other is Programmer errors, respectively.

  • Operational errors: The Operational errors represent the problems with runtime whose outputs are predictable and should be dealt appropriately. Operational errors do not mean that the application itself has bugs but indicates that developers must carefully handle these errors. For Example, the operational errors include "an invalid input for an API endpoint", "out of memory", and many more.
  • Programmer errors: The Programmer errors represent the unexpected bugs in code that are written poorly. The Programmer errors indicate that the application itself has some unsolved issues, and codes are miswritten. For Example, calling an asynchronous function without a callback or trying to read the "undefined" property. To fix the issue, the syntax has to be modified or changed. The developer makes this bug; thus, it is not an operational error.

To Understand the concept of Error Handling, let's start with creating exceptions.

Creating Exceptions

To create an exception, we use the 'throw' keyword following the value:

throw value;

Once JavaScript executes the above line, the normal flow of the program stops, and the control is held back to the closest exception handler. Any JavaScript value used as the client-side code value can be a number, a string, or an object.

Note: Generally, we throw Error objects in Node.js instead of throwing any strings.

Error objects

Error objects capture a "stack trace" specifying the point in the code at which the Error was instantiated and may give a text description of the Error.

All the errors created by Node.js, including JavaScript and all system errors, will either be inherited from or instances of the Error class provided in the Error core module:

throw new Error(‘The Access is Denied’);

or

class AccessIsDeniedError extends Error {
    //...
  };
  throw new AccessIsDeniedError();

Handling Exceptions

The try/catch statement is used as an exception handler. The corresponding catch block handles any exception that appear

s in the code line's syntax comprised in the try block.

try {
    // the lines of code
  } catch (ExceptionValue) {};

To catch various types of errors, we can add multiple handlers.

Catching the Uncaught Exceptions

The program will crash if an uncaught exception gets thrown when the program is being executed. The uncaughtException event is used in the process object to solve such a situation.

process.on('uncaughtException', err => {
    console.error('Uncaught error has been reported!', err);
    process.exit(1) // required (as per the docs of Node.js)
  });

We don't have to import the built-in process module for the procedure, as it's injected automatically.

Exceptions using promises

We can use promises to chain different operations and handle the errors at the end:

doOperation1()
  .then(doOperation2)
  .then(doOperation3)
  .catch(err => console.error(err))

So, a question pops up in one's head is: How do we know where the Error occurred? Well, we don't know, but whenever we call (doOperationX), we can handle errors in each of the functions and can be called outside of the catch handler by throwing a new error in the error handler:

const doOperation1 = () => {
    //...
    try {
      //...
    } catch (err) {
      //... handling the error locally
      throw new Error(err.message)
    };
    //...
  };

We can break the chain without handling the errors in the function we call but, locally, we can process the exception by creating a function in each then():

doOperation1()
  .then(() => {
    return doOperation2().catch(err => {
      // error handling
      throw err; // breaking the chain!
    });
  })
  .then(() => {
    return doOperation2().catch(err => {
      // error handling
      throw err; // breaking the chain!
    });
  })
  .catch(err => console.error(err));

Handling Error using async/await

We still need to catch errors while using async/await in the following manner:

async function someOperation() {
    try {
      await someOtherOperation()
    } catch (err) {
      console.error(err.message);
    }
  };