Error Handling in TypeScript: From try/catch to Complex Scenarios
TypeScript, being a superset of JavaScript, inherits and extends error handling mechanisms. Proper exception handling is the key to a stable and predictable application. In this article, we will cover all aspects of working with errors: from basic try/catch to creating your own typed error classes and advanced strategies.
Basic Syntax: try, catch, finally
The main error handling mechanism in TypeScript (as in JavaScript) is the try...catch...finally construct. It allows you to catch exceptions that occur in the code and respond to them.
function parseJSON(data: string): object { try { return JSON.parse(data); } catch (error) { console.error('JSON parsing error:', error); return {}; }}
parseJSON('{"name": "TypeScript"}'); // { name: 'TypeScript' }parseJSON('invalid JSON'); // {} (error caught)The finally block executes in any case — both after successful completion of try and after catching an error. This is the ideal place for resource cleanup (closing files, connections).
function processData(data: string): void { let resource: any = null; try { resource = acquireResource(); // ... dangerous code } catch (error) { console.error('Processing error:', error); } finally { if (resource) { resource.release(); // guaranteed cleanup } }}Error Typing: The unknown Problem
A key feature of TypeScript — in the catch block, the error variable has the type unknown by default (since TS 4.0). This is intentional, to force the developer to explicitly check the error type rather than assuming it is Error.
try { // some code} catch (error) { // error has type unknown // Error: property 'message' does not exist on type 'unknown' // console.log(error.message);
// Correct approach — type checking if (error instanceof Error) { console.log('Message:', error.message); } else { console.log('Unknown error:', error); }}Why is this important? In JavaScript, anything can end up in the catch block: a string, number, object, null, or even undefined. Mandatory type checking protects against runtime errors.
Creating Custom Error Classes
For complex applications, it is often necessary to distinguish between types of errors. In TypeScript, it is easy to create a hierarchy of custom error classes extending the built-in Error.
// Base class for application errorsclass AppError extends Error { public readonly statusCode: number; public readonly isOperational: boolean;
constructor(message: string, statusCode: number = 500, isOperational: boolean = true) { super(message); this.name = this.constructor.name; this.statusCode = statusCode; this.isOperational = isOperational;
// Restore the prototype (important for instanceof) Object.setPrototypeOf(this, new.target.prototype); }}
// Specific error typesclass ValidationError extends AppError { public readonly field: string;
constructor(message: string, field: string) { super(message, 400); this.field = field; }}
class NotFoundError extends AppError { constructor(message: string = 'Resource not found') { super(message, 404); }}
// Usagefunction getUser(id: number): User { if (id <= 0) { throw new ValidationError('ID must be a positive number', 'id'); } // ... search for user throw new NotFoundError