Error Handling in JavaScript: try/catch, async/await, and Advanced Patterns

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

Advanced Error Handling in JavaScript: From try/catch to Global Interceptors

\n\n

Error handling is not just about "wrapping code in try/catch". In real production Node.js applications, proper exception management helps avoid server crashes, correctly log issues, and provide users with clear messages. In this article, we will cover advanced error handling techniques in JavaScript that go beyond basic syntax.

\n\n

1. Deep Dive into try/catch and finally

\n\n

The classic try...catch...finally construct remains the foundation of synchronous handling. However, at an advanced level, it is important to understand the nuances of its operation.

\n\n
function riskyOperation(value) {\n  if (typeof value !== 'number') {\n    throw new TypeError('Argument must be a number');\n  }\n  if (value < 0) {\n    throw new RangeError('Value cannot be negative');\n  }\n  return Math.sqrt(value);\n}\n\ntry {\n  const result = riskyOperation(-4);\n  console.log('Result:', result);\n} catch (error) {\n  if (error instanceof TypeError) {\n    console.error('Type Error:', error.message);\n  } else if (error instanceof RangeError) {\n    console.error('Range Error:', error.message);\n  } else {\n    console.error('Unknown error:', error);\n  }\n} finally {\n  console.log('Cleanup: always executed');\n}
\n\n

Key points:

\n
    \n
  • The finally block always executes, even if there is a return in try or catch.
  • \n
  • Use built-in error classes (TypeError, RangeError, SyntaxError) for precise identification.
  • \n
  • Do not catch errors you cannot handle — let them bubble up.
  • \n
\n\n

2. Custom Error Classes and Extending Error

\n\n

For large projects, standard error types are insufficient. Creating your own classes allows you to add metadata (HTTP status, error code, context) and simplifies filtering.

\n\n
class AppError extends Error {\n  constructor(message, statusCode, code) {\n    super(message);\n    this.name = 'AppError';\n    this.statusCode = statusCode;\n    this.code = code;\n    Error.captureStackTrace(this, this.constructor);\n  }\n}\n\nclass ValidationError extends AppError {\n  constructor(message, field) {\n    super(message, 400, 'VALIDATION_ERROR');\n    this.field = field;\n  }\n}\n\nclass DatabaseError extends AppError {\n  constructor(message, query) {\n    super(message, 500, 'DB_ERROR');\n    this.query = query;\n  }\n}\n\n// Usage\ntry {\n  throw new ValidationError('Email is required', 'email');\n} catch (error) {\n  if (error instanceof ValidationError) {\n    console.log(`Field: ${error.field}, Status: ${error.statusCode}`);\n  }\n}
\n\n

Why this matters: custom errors allow centralized handling of different scenarios (e.g., in Express.js middleware) and returning structured JSON to the client.

\n\n

3. Error Handling in Asynchronous Code: async/await and Promise

\n\n

In Node.js, most code is asynchronous. Errors in Promise and async/await require a special approach.

\n\n

3.1. Promise Handling

\n\n
function fetchUserData(userId) {\n  return new Promise((resolve, reject) => {\n    if (userId <= 0) {\n      reject(new RangeError('Invalid user ID'));\n    }\n    // simulate DB request\n    setTimeout(() => resolve({ id: userId, name: 'Alice' }), 100);\n  });\n}\n\nfetchUserData(-1)\n  .then(user => console.log(user))\n  .catch(error => {\n    console.error('Promise rejected:', error.message);\n  })\n  .finally(() => console.log('Cleanup after promise'));
\n\n

3.2. async/await Handling

\n\n
async function getUserProfile(userId) {\n  try {\n    const user = await fetchUserData(userId);\n    const posts = await fetchUserPosts(user.id                

Blogs

Book Recommendations