Just like PHP, JavaScript supports Exceptions - only they're called Errors. However, due to the asynchronous nature of Node.js, the classic try/catch strategy doesn't work. To catch asynchronous errors, Node strongly encourages a particular signature for callback functions. I'll explain all that shortly, but now I have a confession to make.
I don't eat meat. It's not because I like animals too much - I don't believe chickens have a soul. It's just that I ate a lot of meat previously in my life, and if every person on this planet eats as much meat as I did, the place will become awful to live in. If you don't believe me, go watch this video on YouTube and thank Prof. Albert Bartlett for the depression. So I quit eating flesh a year ago, and so far my teeth haven't fallen or something.
But to be honest, I still eat meat every once in a while. You see, vegetarians have to take pills to compensate for the nutriments they don't get from the meat. I prefer to eat meat than pills. And since the Sunday meal is often a family moment, and since meat MUST be on the menu, I eat meat almost every Sunday.
And I like meat. That means that I love the Sunday meals, whether it's veal sauté with white wine, or roasted chicken with French fries. And last time I ate too much of it. Then things started going wrong.
I won't expose in details the miserable Sunday afternoon I spent after that. However, since the audience of this blog is mostly composed of programmers, I can write what happened in PHP.
JavaScript is another good way to express my misery. And since Node.js puts a dose of asynchronicity on top of JavaScript, it's a good opportunity to show that digestion can take time. Here is the equivalent of the previous class, written for Node.js:
In digest()
, setTimeout()
is there to simulate an asynchronous processing. Asynchronous functions don't use the return
statement to return their result. Instead, they call the given callback with the result as parameter. That's why digest()
ends with callback(nutriments)
instead of return $nutriments
as in PHP. Therefore, eat()
has to provide a callback when calling digest()
. When all the elements are processed, eat()
can return true
by calling its own callback.
Tip: If you don't understand why the line var body = this
is necessary, you've not yet encountered the First Gotcha of JavaScript. Head to my article on "this" for more details.
Second Tip: The test if (processedElements == meal.length)
is a way to determine when the asynchronous meal digestion is complete. The need to resynchronize asynchronous processing arises quite often in Node.js, and instead of using boilerplate code similar to this one, you should definitely use the async
module. It offers resynchronization, and many other things. In my opinion, it should be part of the Node.js core.
The digest()
function is asynchronous, remember? So when Node executes this piece of code from eat()
:
Node iterates over every element in the meal, executes HumanBody.digest()
on all of them, then ends. That means that if a problem of digestive capacity occurs while in digest()
, it will be after the end of the eat()
execution, and that means after the try {} catch {}
statement is finished. The Error
will never get caught, and this is dramatic. Imagine a human body unable to vomit even when filled up over the limit? Explosion!
In asynchronous programming, the communication channel between the caller function and the callee is the callback. The only solution to overcome the uncaught error is to pass the Error
to the callback. So the eat()
and digest()
functions must be refactored as follows:
The important thing is is the new signature of the callback passed to digest()
:
The callback expects that an error may be passed by the asynchronous digestion. And it's the responsibility of the callback to deal with this error if it exists, and to proceed with normal operations otherwise.
And it's the only way to catch an error thrown in an asynchronous function. So forget about try {} catch {}
in Node.js. You will use them very seldom, while callbacks expecting errors are everywhere.
What happens with the asynchronous digestion can happen in every asynchronous function in Node.js. Thats's why the core asynchronous functions always show err
as the first argument of every callback. If the operation was completed successfully, then the first argument will be null
or undefined
. Here is an example with the equivalent of PHP's realpath()
:
When you design your own asynchronous functions, follow this rule of thumb and always put the error as first argument of the callback. It's also a good way to remember that you have to deal with the error cases first.
There is still a flaw in the Node.js HumanBody
implementation. If something bad happens in the middle of the meal digestion, the vomiting is triggered and the main callback is called with false
as argument. But the forEach()
loop continues. That means that the callback may be called a second time with true
as argument. The correction is left as an exercise for the reader.
As for my digestive problems, they're over. But "Once bitten, twice shy", so I always think that something bad may happen to me when eating meat, and I'm very careful.
If you don't want to give up meat, you should be careful, too. Always expect that something bad may happen, and you will design robust and effective programs - even in asynchronous Node.js.
Also in this series: Node.js for PHP Programmers #2: Modules, Packages, and the Strawberry House and Node.js for PHP Programmers #4: Streams.
Tweet
Published on 15 Apr 2012
with tags development JavaScript NodeJS php