JS: Asynchronous programming
Theory: Error handling
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:
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:
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:
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:
In the call chain, you should do a check at each level:
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:
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):

