Register to get access to free programming courses with interactive exercises

Exceptions JS: Introduction to Object Oriented Programming (OOP)

Error handling is a vast and distinct programming subject. So far, we've managed to escape it, but in the real world, where applications contain thousands, and tens and hundreds of thousands (if not millions) of lines of code, error handling affects many things: how easy it is to modify and extend the code, and whether the program behaves properly in different situations.

In this lesson, we'll look at exceptions. But before we explore new concepts, let's talk about errors in general.

In JavaScript, strings have a method called text.indexOf(str). It looks for a substring str within text text and returns the index of the beginning of this substring in the text. What happens if the substring is not found? Is this behavior a mistake? No. This is normal function behavior. There's nothing wrong with not finding a substring. Imagine any text editor and a search engine within it. Nothing being found is a regular phenomenon that lefts the program employable.

By the way, look in the documentation of this function, how does it tell you that the substring wasn't found?

Another situation. These editors have an "open file" function. Imagine if anything went wrong when you attempted to open the file like it was destroyed. Is this error or not? Yes, there was an error here, but it wasn't a programming error. No matter what the programmer intends, this kind of error is always possible. It is unavoidable. The programmer's sole option is to handle it correctly.

Another interesting question is, "how critical is this error?” Should it cause the entire application to stop? In poorly written applications, where error handling is flawed, this situation will cause the whole application to crash and terminate. In a well-written application, nothing wrong will happen. The user will see a warning that the file is unreadable and will be able to choose further actions, such as trying to read it again or performing another action.

The preceding has serious consequences. At different levels, this event may either represent an error or be completely normal. For instance, there will be an error from the standpoint of a function if it is supposed to read a file but fails to do so. Should the entire application be terminated as a result? It should not, as we discovered above. It's up to the application that uses the feature to decide how critical the situation is, but not the feature itself.

Return codes

In languages that appeared before 1990 (approximately), error handling was done through a mechanism wherein a function returns a special value. For example, in C, if a function fails, it must return a special value, either NULL or a negative number. The value of this number tells you what kind of error occurred. For example:

int write_log()
    int ret = 0; // return value 0 if success
    FILE *f = fopen("logfile.txt", "w+");

    // Checking if the file can be opened
    if (!f)
        return -1;

    // Checking that we haven't reached the end of the file
    if (fputs("hello logfile!", f) != EOF) {
        // continue using the file resource
    } else {
        // File has ended
        ret = -2;

    // Failed to close the file
    if (fclose(f) == EOF)
        ret = -3;

    return ret;

Note the conditional constructs and the constant assignment by the ret variable. Every highly unsafe procedure needs to be successfully tested. The function returns a unique code if something goes wrong.

And this is where the problems begin. As life shows, in most situations, the error is not handled where it occurred, nor even at a level higher. Consider a function called A that calls code throwing an error, and it should be able to handle it correctly and notify the user. The error itself occurs inside function E, which is called inside A indirectly through a chain of functions: A => B => C => D => E. Think about what this might lead to. All functions in this chain must be aware of the error, catch it, and return the error code even though they don't handle it. As a result, there's so much code for dealing with bugs that the code doing the original task falls by the wayside.

It's important to note that there are error-handling techniques that follow the returning principle but don't suffer from these drawbacks. For example, the Either Monad.


It was in this context that the exception method arose. Its main purpose is to pass the error from where it originated to where it can be processed, bypassing all the intermediate levels. In other words, the exception mechanism unwinds the call stack by itself.

There are two things to remember about exceptions: the code where the error occurred throws an exception, and the code where the error is handled - where it's caught.

// A function that can throw an exception
const readFile = (filepath) => {
  if (!isFileReadable(filepath)) {
    // throw is a way to throw an exception
    throw new Error(`'${filepath}' is not readable`);
  // ...

// Somewhere else in the program

const run = (filepath) => {
  try {
    // The function that calls readFile. Perhaps not directly, but through other functions.
    // It's not important for the exception mechanism.
  } catch (e) {
    // This block is only executed if an exception is thrown in the try block
  // If there's code here, it'll continue to run

The exceptions themselves are Error objects. These objects contain the message passed to the constructor, the stack trace, and other useful data inside.

The exception is thrown by itself with the throw keyword::

const e = new Error('Any text here');
throw e; // The exception can be created separately, or it can be created straight away when throw is used

throw stops the code from being executed any further. In this sense, it's similar to return, but unlike return , it interrupts not only the current function, but the entire code, up to the nearest catch block in the call stack.

The try/catch block is usually placed at the top level of the program, but this isn't crucial. It's likely that there are several intermediate blocks that can catch errors and re-initiate them. This topic is quite complex and requires a certain amount of experience.

Are there any more questions? Ask them in the Discussion section.

The Hexlet support team or other students will answer you.

About Hexlet learning process

For full access to the course you need a professional subscription.

A professional subscription will give you full access to all Hexlet courses, projects and lifetime access to the theory of lessons learned. You can cancel your subscription at any time.

Get access
hours of theory

Sign up

Programming courses for beginners and experienced developers. Start training for free

  • 130 courses, 2000+ hours of theory
  • 1000 practical tasks in a browser
  • 360 000 students
By sending this form, you agree to our Personal Policy and Service Conditions

Our graduates work in companies:

<span class="translation_missing" title="translation missing:">Bookmate</span>
<span class="translation_missing" title="translation missing:">Healthsamurai</span>
<span class="translation_missing" title="translation missing:">Dualboot</span>
<span class="translation_missing" title="translation missing:">Abbyy</span>
Suggested learning programs
Development of front-end components for web applications
10 months
from scratch
Start at any time

Use Hexlet to the fullest extent!

  • Ask questions about the lesson
  • Test your knowledge in quizzes
  • Practice in your browser
  • Track your progress

Sign up or sign in

By sending this form, you agree to our Personal Policy and Service Conditions
Toto Image

Ask questions if you want to discuss a theory or an exercise. Hexlet Support Team and experienced community members can help find answers and solve a problem.