Error Handling in JavaScript: try/catch and Beyond — A Complete Guide

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

Introduction: Why Error Handling is the Foundation of Reliable Code

Errors in code are inevitable. They arise from invalid user data, network failures, missing files on the server, or simple developer typos. In the Node.js environment, where code executes asynchronously and often interacts with external resources (databases, file system, APIs), an unhandled error can crash the entire process. Proper error handling in JavaScript turns a fragile application into a resilient service. In this article, we will cover all key mechanisms: from classic try/catch to creating custom error classes and working with asynchronous code.



1. Basics: try, catch, finally and throw

The basic block for exception handling in JavaScript is the try...catch...finally construct. It allows you to "catch" an error that occurred in a "dangerous" section of code and respond to it correctly without interrupting the execution of the entire program.



Syntax and operation of try/catch

Code that might throw an exception is placed inside the try block. If an error occurs, control is immediately transferred to the catch block, where the error object is available. If there is no error, the catch block is ignored.

try {  const data = JSON.parse('{"name": "Anna"}'); // Valid JSON  console.log(data.name); // Outputs: Anna} catch (error) {  console.error('Parsing error:', error.message);}// Execution continues


Now let's simulate an error:

try {  const data = JSON.parse('invalid json'); // Error!  console.log(data.name);} catch (error) {  console.error('Parsing error:', error.message);  // Output: Parsing error: Unexpected token 'i', ... is not valid JSON}console.log('Program is alive!'); // This code will execute


The finally block: code that always runs

The finally block executes regardless of whether an error occurred or not. It is ideal for releasing resources (closing files, database connections) or cleaning up timers.

let resource = null;try {  resource = acquireResource(); // Acquire resource  resource.doSomething(); // Dangerous operation} catch (error) {  console.error('Error:', error);} finally {  if (resource) {    resource.close(); // Always close the resource    console.log('Resource released.');  }}


The throw operator: creating your own errors

You can "throw" an error yourself using throw. This is useful for validating input data or interrupting execution when business logic is violated. You can pass anything as an argument, but the best practice is to use the Error object or its descendants.

function divide(a, b) {  if (typeof a !== 'number' || typeof b !== 'number') {    throw new Error('Arguments must be numbers');  }  if (b === 0) {    throw new Error('Division by zero is not allowed');  }  return a / b;}

try { console.log(divide(10, 0));} catch (error) { console.error('Error:', error.message); // Error: Division by zero is not allowed}


2. Advanced Techniques: Custom Error Classes and Typing

In large projects, simply using the standard Error is not enough. You need to distinguish between network errors, validation errors, authorization errors, and others. For this, custom classes extending the built-in Error are created.



Creating custom errors

class ValidationError extends Error {  constructor(message) {    super(message);    this.name = 'ValidationError'; // Unique name for checking  }}

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

function fetchUser(id) { if (!id) { throw new Valida

Blogs

Book Recommendations