ProgrammingBackend Developer

Describe how the custom error types mechanism works in TypeScript. How to properly declare custom error classes, why is it important to specify their type, and how to avoid pitfalls with 'instanceof' and error serialization?

Pass interviews with Hintsage AI assistant

Answer.

Background: In JavaScript, standard errors are described via the Error class. In large TypeScript projects, it is important to use your own error hierarchy for precise handling of situations, but there are nuances with types, inheritance, and 'instanceof' behavior.

Problem: When inheriting from Error, there can be difficulties: prototype may be lost, 'instanceof' may not work correctly, types may be described incorrectly, and serialization often leads to loss of stack trace. Without explicitly defining properties, bugs may arise when handling errors.

Solution: Correctly define custom errors by extending the Error class. It is necessary to explicitly specify the name, manually restore the prototype (important for ES5 and when compiling to CommonJS), and type the error fields.

Code example:

class ValidationError extends Error { code: number; constructor(message: string, code: number = 400) { super(message); Object.setPrototypeOf(this, ValidationError.prototype); this.name = 'ValidationError'; this.code = code; } } function process(user: string) { if (!user) throw new ValidationError('User required', 401); }

Key features:

  • Explicit prototype restoration to support 'instanceof' even after transpiling.
  • Typing of custom error properties.
  • Individual class for each logical group of errors.

Trick questions.

Why can't you skip the call to Object.setPrototypeOf(this, ...)?

If you don't call Object.setPrototypeOf(this, Class.prototype), 'instanceof' ValidationError will not work when compiled to ES5/CommonJS or Babel. This will cause catch blocks for ValidationError not to catch the error.

class CustomErr extends Error {} const err = new CustomErr('msg'); console.log(err instanceof CustomErr); // false without setPrototypeOf

Can the name field of a custom error be omitted?

If you do not set the this.name property, the error stack and logs will be incorrect, complicating the search for causes and the classification of errors.

Should errors be serializable, and if so — how?

Errors should be correctly serializable (for example, for logging or network transfer), otherwise JSON.stringify(new Error()) will not produce message and stack. You should override the toJSON method.

class SerializableError extends Error { toJSON() { return { name: this.name, message: this.message, stack: this.stack }; } }

Common mistakes and anti-patterns

  • Absence of Object.setPrototypeOf leads to incorrect instanceof behavior
  • Ignoring name and custom properties
  • Serializing error without implementing toJSON: loses stack/message

Real-life example

Negative case

In a project, we simply did class MyError extends Error without restoring the prototype. Errors were caught via if (err instanceof MyError), but this did not work, and the code silently skipped handling critical situations.

Pros:

  • The code looked concise
  • There was no boilerplate

Cons:

  • instanceof did not work
  • Exceptions were not logged correctly
  • With the change from ts->js transpiler, incompatibility arose

Positive case

We implemented a correct CustomError, explicitly set name, code, and toJSON, covered the handling of different types of errors with tests. In logs and catch handlers, the structure of errors became clear, thereby reducing the time spent on bug searches.

Pros:

  • Clear typed error hierarchy
  • Reliable serialization
  • Cross-platform compatibility between browser and Node.js

Cons:

  • Template logic appeared in each CustomError class