Register to get access to free programming courses with interactive exercises

Error handling JS: Asynchronous programming

Reading and writing files, retrieving data over the network, and executing HTTP requests are all I/O operations. Through them, the program interacts with the external environment.

The external environment has a wide variety of rules that we must follow. For example, a program must have access to a file to read it successfully. For writing, it needs free space on the disk. To perform queries over the network, you need a connection to the network.

There are dozens or even hundreds of such conditions. Failure to do at least one of them will lead to an error. Look at this impressive list of several hundred errors of all kinds.

In JavaScript, error handling works using an exception mechanism. Some functions excite them, others process them through try..catch.

It is the case with synchronous code. With asynchronous code, the standard mechanism no longer works.

Think about how the code below will work:

import fs from 'fs';

try {
  // We try to read the directory and get an error
  fs.readFile('./directory', 'utf-8', () => {
    callUndefinedFunction();
  });
} catch (e) {
  console.log('error!')
}

Since try/catch only works with code from the current call stack, it won't be able to intercept anything in another stack. Therefore, we won't see an error! message, though the error itself will appear on the screen:

callUndefinedFunction();
^

ReferenceError: callUndefinedFunction is not defined
    at ReadFileContext.fs.readFile [as callback] (/private/var/tmp/index.js:6:5)

You can see from the output that we have called the callback in its call stack, which started inside the readFile() function. It means that using try/catch in asynchronous code with callbacks is useless. This construction is just not applicable here.

Let's observe what the code below displays:

import fs from 'fs';

try {
  // We try to read the directory and get an error
  fs.readFile('./directory', 'utf-8', () => {
    console.log('finished!');
  });
} catch (e) {
  console.log('error!');
}

The correct answer is finished!. It seems odd, given that the error occurred inside the readFile() function, not in the callback. It happens because the contents of the readFile() function don't belong to the current call stack.

Asynchronous functions always deal with the external environment (operating system). In other words, any asynchronous function could potentially end with an error. It doesn't matter whether it returns some data or not; an error can always occur.

This is the reason why all asynchronous functions take err as the first parameter, and you have to check for it manually. If we get a null, there's no error. This is a major agreement, to which not only the developers of the standard library adhere but also all developers of third-party solutions:

fs.readFile('./directory', 'utf-8', (err, data) => {
  // Any file reading errors: access, no file, directory instead of file
  // Null can implicitly be reduced to false, so this check is sufficient,
  // Any other answer is treated as true
  if (err) {
    console.log('error!');
    return; // guard expression
  }

  console.log('finished!')
});

In the call chain, you should do a check at each level:

import fs from 'fs';

fs.readFile('./first', 'utf-8', (error1, data1) => {
  if (error1) {
    console.log('error in first file')
    return;
  }
  fs.readFile('./second', 'utf-8', (error2, data2) => {
    if (error2) {
      console.log('error in second file')
      return;
    }
    fs.writeFile('./new-file', `${data1}${data2}`, (error3) => {
      if (error3) {
        console.log('error during writing')
        return;
      }
      console.log('finished!');
    });
  });
});

The same code placed inside the function looks a little different. As soon as an error occurs, we call the main callback and send the error there. If there's no error, we still call the original callback and pass null into it. We must call it. Otherwise, the external code won't wait until the end of the operation. The following calls are no longer made:

import fs from 'fs';

const unionFiles = (inputPath1, inputPath2, outputPath, cb) => {
  fs.readFile(inputPath1, 'utf-8', (error1, data1) => {
    if (error1) {
      cb(error1);
      return;
    }
    fs.readFile(inputPath2, 'utf-8', (error2, data2) => {
      if (error2) {
        cb(error2);
        return;
      }
      fs.writeFile(outputPath, `${data1}${data2}`, (error3) => {
        if (error3) {
          cb(error3);
          return;
        }
        cb(null); // do not forget the last successful call
      });
    });
  });
};

We can shorten the last call. If there was no error at the very end, then calling cb(error3) will work the same way as cb(null). So, all the code of the last callback can be reduced to calling cb(error3):

fs.writeFile(outputPath, `${data1}${data2}`, cb);
// Which is equivalent to fs.writeFile(outputPath, `${data1}${data2}`, error3 => cb(error3));

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

The Hexlet support team or other students will answer you.

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
130
courses
1000
exercises
2000+
hours of theory
3200
tests

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:

Bookmate
Health Samurai
Dualboot
ABBYY
Suggested learning programs
profession
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.