Error handling in Node.js

Error handling in Node.js

Without errors, we can't imagine developers' lives. Whenever we create or build an application we we have to face several errors. And this is very annoying. In this article, we will focus on how we can manage or avoid errors effectively.

Without error handling, we can't properly read the errors. Because the error language is very weird. So for better understanding, we should handle the errors.

Types of Error

  1. Syntax Errors: These are errors that occur when you have a mistake in your code's syntax. Common examples include missing semicolons, parentheses, or curly braces.

  2. Runtime Errors: Runtime errors occur when your code is syntactically correct, but there's an issue during execution. Common examples include trying to access an undefined variable or calling a function that doesn't exist.

  3. Logical Errors: These are the trickiest errors to spot because they don't result in immediate crashes or error messages. Instead, your program may produce unexpected or incorrect results due to a flaw in your logic.

  4. Asynchronous Errors: Node.js is known for its asynchronous, non-blocking I/O operations. Errors that occur in asynchronous code can be challenging to handle. Common examples include callback errors, promise rejections, and unhandled promise rejections.

  5. System Errors: These errors are related to the underlying system or environment in which your Node.js application is running. Examples include running out of memory, file system errors, and network errors.

  6. Custom Errors: You can create custom error objects in Node.js to represent specific error conditions in your application. This can make error handling more expressive and help you identify and respond to specific issues.

  7. Unhandled Errors: Errors that are not properly caught or handled can lead to unhandled exceptions, which can crash your

    We can handle these Errors

    1. Syntax Errors:

      • Careful Code Review: The best way to handle syntax errors is to prevent them in the first place by reviewing your code thoroughly.

      • Linter: Use a code linter like ESLint to catch and fix syntax errors automatically.

    2. Runtime Errors:

      • Debugging: Use Node.js built-in debugging tools like console.log or a debugger to inspect variables and trace the issue.

      • Error Handling: Wrap the problematic code in try-catch blocks to catch and handle runtime errors gracefully.

      • Logging: Log error details to a file or console for debugging purposes.

    3. Logical Errors:

      • Code Review: Carefully review your code logic, algorithms, and calculations to identify and fix logical errors.

      • Unit Testing: Write unit tests to validate the correctness of your code and catch logic errors early in development.

    4. Asynchronous Errors:

      • Promises: Use .catch() with promises to handle promise rejections and errors in asynchronous code.

      • Async/Await: Wrap asynchronous code in a try-catch block when using async/await.

      • Event Emitters: Add error event listeners to event emitters to catch and handle errors.

    5. System Errors:

      • Error Handling: Implement robust error handling for system-related errors, like checking for file existence before reading/writing files.

      • Resource Management: Monitor and manage system resources like memory to avoid running out of resources.

    6. Custom Errors:

      • Error Classes: Create custom error classes by extending the Error class to represent specific error conditions in your application. This can make error handling more expressive.
    7. Unhandled Errors:

      • Global Error Handling: Implement a global error handler using the process.on('uncaughtException') event to catch unhandled exceptions and log them.

      • Promise Rejections: Use process.on('unhandledRejection') to catch unhandled promise rejections and handle them appropriately.

      • Exit Gracefully: When critical errors occur, consider gracefully shutting down your application to prevent further issues.

Some Practical ways of handling Errors are.

  1. try…catch blocks
try {
  const result = someFunction();
  // Use 'result' here
} catch (error) {
  // Handle the error
  console.error("An error occurred:", error.message);
}
  1. Callbacks

    Callbacks are commonly used with the error-first pattern, where the first argument of the callback function is reserved for an error object (if an error occurs), and the subsequent arguments contain the result or data. Here's how you can use callback functions to handle errors:

function readFileAndDoSomething(filePath, callback) {
  // Read a file asynchronously
  fs.readFile(filePath, 'utf8', (error, data) => {
    if (error) {
      // Pass the error to the callback
      callback(error);
    } else {
      // Call the callback with the data
      callback(null, data);
    }
  });
}
// Usage
readFileAndDoSomething('example.txt', (error, data) => {
  if (error) {
    console.error('Error:', error.message);
    // Handle the error here
  } else {
    console.log('File data:', data);
    // Continue processing the data here
  }
});

I use a Demo .tsxt file for demo purpose

function readFile (filePath,callback){
fs.readFile('data.txt','utf8',(error,data)=>{
    if(error){
        // passing the error to the callback
    callback(error);
    }
    else{
        // call back with data 
        callback(null,data);
    }
})}
readFile('data.txt',(error,data)=>{
    if(error){
        console.error('Error',error.message)
        // handling the error here
    }
    else{
        console.log('File Data',data);
        // continue Process
    }
 })

  1. Promises

    Promises have two possible states: resolved (when the operation succeeds) and rejected (when the operation fails).

 // Promises
const fs = require('fs');
const fsPromises = require('fs').promises;

fs.promises.readFile ('data.txt').then((result)=>{
console.log("Data " + result)
})
.catch((error)=>{
    console.log(error);
})

  1. Promisified Functions:

    You can promisify these functions using the util.promisify utility in the util module (available in Node.js).

   // Require this module
const util = require('util');

const readFileAsync = util.promisify(fs.readFile);
readFileAsync('data.txt', 'utf8')
  .then((data) => {
    console.log('File data:', data);
  })
  .catch((error) => {
    console.error('Error:', error.message);
  });

  1. Event Emitter

    When an error occurs in your code, you can emit an error event using the emit method of your EventEmitter instance.

const EventEmitter  = require('events');
const myEmitter = new EventEmitter

// Emit an error event
myEmitter.emit('error', new Error('Something went wrong'));

//Listening for Errors:
myEmitter.on('error', (error) => {
  console.error('An error occurred:', error.message);
  // Handle the error here
});
const EventEmitter  = require('events');
const fileEmitter = new EventEmitter
//  create a funciton which read a filePath
function readFileAndEmitevent (filePath){
    fs.readFile(filePath,'utf-8',(error,data)=>{
        if(error){
            // if error occured emit and event 
            fileEmitter.emit('error',error);
        }else{
            // emit data event 
            fileEmitter.emit('data',data);
        }
    })
}
// attach an error event listner
fileEmitter.on('error',(error)=>{
console.error('An Error couured');
})

// attach an data event listner
fileEmitter.on('data',(data)=>{
    console.log('File data:',data);
})

 // function call 
 readFileAndEmitevent('data.txt');

Thank you for reading my content. Be sure to follow and comment on what you want me to write about next 🤓

Did you find this article valuable?

Support Saurabh verma by becoming a sponsor. Any amount is appreciated!