Introduction: Why Error Handling is the Foundation of Reliable Code
Any application running on Node.js will sooner or later encounter exceptional situations: invalid user input, network failure, missing file on disk, or incorrect data from the database. Without proper error handling, these situations lead to server crashes, memory leaks, or undefined behavior. In JavaScript, especially in the Node.js environment, the try/catch mechanism is a fundamental tool for building fault-tolerant systems. In this article, we will cover not only the classic try/catch but also advanced techniques: custom error classes, working with promises and async functions, as well as best practices for logging.
Basics: try, catch, and finally blocks
The basic syntax for error handling in JavaScript looks like this:
try { // Code that may throw an exception const data = JSON.parse('{"name": "Node.js"}'); console.log(data.name);} catch (error) { // Error handling console.error('An error occurred:', error.message);} finally { // Code that will execute in any case console.log('Finally block executed');}How it works:
try— contains potentially dangerous code.catch— catches the exception if it occurs. Theerrorparameter receives the error object.finally— always executes, regardless of whether there was an error or not. Ideal for closing files, releasing resources, or completing transactions.
It is important to remember: finally will execute even after a return in the try or catch block.
Types of Errors in JavaScript
Built-in error constructors help classify exceptions more precisely:
Error— the base class for all errors.SyntaxError— a syntax error (e.g., invalid JSON).TypeError— an incorrect data type (e.g., calling a non-function).ReferenceError— referencing a non-existent variable.RangeError— exceeding allowable limits (e.g., recursion).
Example of handling different types:
try { const obj = null; console.log(obj.property); // TypeError} catch (err) { if (err instanceof TypeError) { console.log('Type error:', err.message); } else if (err instanceof ReferenceError) { console.log('Variable not found'); } else { console.log('Unknown error:', err); }}Generating Custom Errors: the throw Operator
Sometimes it is necessary to explicitly indicate that an exceptional situation has occurred in the program. The throw operator is used for this. It can throw anything: a string, a number, an object, but it is best to throw an instance of the Error class or its descendant.
function divide(a, b) { if (b === 0) { throw new Error('Division by zero is not allowed'); } if (typeof a !== 'number' || typeof b !== 'number') { throw new TypeError('Arguments must be numbers'); } return a / b;}
try { const result = divide(10, 0); console.log(result);} catch (err) { console.error(err.name + ': ' + err.message);}Why is it better to throw an Error rather than a string? The Error object contains a call stack (stack), which is critically important for debugging in Node.js. A string will not provide such information.
Creating Custom Error Classes
In real projects (e.g., an Express web server), it is convenient to have custom error types for different situations: ValidationError, NotFoundError, DatabaseError. This simplifies centralized handling.
class ValidationError extends Error {