Error Handling in JavaScript: try/catch and Advanced Techniques in Node.js

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Error Handling in JavaScript: try/catch and Beyond



Error handling is one of the key tasks in JavaScript development, especially in the Node.js environment, where the stability of a server application directly impacts user experience. Unhandled exceptions can lead to the entire process crashing and data loss. In this article, we will cover not only the classic try/catch block but also advanced techniques: working with finally, creating custom error types, handling asynchronous errors, and best practices for Node.js.



1. Basics: try, catch and finally



The try/catch block is the fundamental mechanism for catching synchronous exceptions. If an error occurs in the try block, execution is immediately transferred to the catch block. The finally block always executes — regardless of whether there was an error or not. This is convenient for releasing resources (closing files, connections).



const fs = require('fs');

function readConfig(path) { try { const data = fs.readFileSync(path, 'utf8'); console.log('Configuration loaded'); return JSON.parse(data); } catch (err) { console.error('Error reading config:', err.message); return null; } finally { console.log('Read attempt completed'); }}

const config = readConfig('./config.json');console.log(config); // null on error


Important: catch only catches errors that occurred in the try block. Errors in catch or finally remain unhandled.



2. Error Types in JavaScript and Node.js



JavaScript has several built-in error types, each inheriting from Error:



  • SyntaxError — a syntax error during code parsing.
  • ReferenceError — referencing a non-existent variable.
  • TypeError — an invalid data type (e.g., calling a non-function).
  • RangeError — a value outside the allowed range.
  • URIError — an error when working with URI (encodeURI/decodeURI).


In Node.js, AssertionError (from the assert module) and system errors (EACCES, ENOENT, etc.) from the fs module are added, which contain the error code in the code property.



try {  require('fs').readFileSync('/nonexistent');} catch (err) {  if (err.code === 'ENOENT') {    console.log('File not found');  } else {    console.log('Other error:', err.message);  }}


3. Creating Custom Errors



For more precise control over the handling logic, it is recommended to create your own error classes that extend Error. This allows you to distinguish business logic errors from system failures.



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

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

function saveUser(user) { if (!user.email) { throw new ValidationError('Email is required', 'email'); } // simulate DB error throw new DatabaseError('Connection timeout', 'INSERT INTO users');}

try { saveUser({ name: 'Ivan' });} catch (err) { if (err instanceof ValidationError) { console.log(`Field ${err.field}: ${err.message}`); } else if (err instanceof DatabaseError) { console.error(`DB Error (${err.query}): ${err.message}`); // retry or logging } else { console.error('Unknown error:', err); }}


Using instanceof allows you to build chains of handlers without resorting to checking string codes.

Blogs

Book Recommendations