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 is not a simple thing, it's got a wide variety of rules that must be followed. For example, a program must have access to a file in order 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. Take a 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. This was 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, 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 the callback was called in its call stack, which started inside the readFile() function. In fact, this means that using try/catch in asynchronous code with callbacks is useless, this construction is simply not applicable here.

What does the code below display?

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!. This seems strange, given that the error occurred inside the readFile() function, not in the callback. This 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). This means that 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, accordingly, you have to check for it manually. If we get a null, there's no error, but if there's no null there is an error. This is a very important 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 have to 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. It must be called, 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
      });
    });
  });
};

The last call can be shortened. If there was no error at the very end, then calling cb(error3) will work the same way as calling cb(null), which means that 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));

Hexlet Experts

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
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:

<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.bookmate">Bookmate</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.healthsamurai">Healthsamurai</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.dualboot">Dualboot</span>
<span class="translation_missing" title="translation missing: en.web.courses.lessons.registration.abbyy">Abbyy</span>
Suggested learning programs

From a novice to a developer. Get a job or your money back!

Frontend Developer icon
Profession
beginner
Development of front-end components for web applications
start anytime 10 months

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.