Recently I’ve noticed a lack of resources on advanced Node.js topics. There are plenty of tutorials on the basics, but you’ll find very little written on maintainable design or scaleable architecture. I created The Node.js Handbook to address this gap. You can read all past topics here.
If the V8 Engine is the heart of your Node.js application, then callbacks are its veins. They enable a balanced, non-blocking flow of control and processing power across applications and modules. But for callbacks to work at scale, developers needed a common, reliable protocol. The “error-first” callback (or “Errorback”) was introduced to solve this problem, and has since become the standard protocol for Node.js callbacks. This post will define this pattern, it’s proper use, and exactly why it is so powerful.
Almost all Node.js code follows this style, so having a dependable callback pattern is crucial. Without one, developers would be stuck looking up and remembering the form and arguments of each and every callback. The error-first pattern was introduced into Node.js core to solve this very problem, and has since spread to become today’s standard. While every use-case has different requirements and responses, the error-first pattern can accommodate them all.
Defining an Error-First Callback
There’s really only one rule for using an error-first callback:
- The first argument of the callback is always reserved for an error object. The following arguments will contain any other data that should be returned to the callback. There is almost always just one object following ‘err’, but you can use multiple arguments if truely needed.
When it’s time to call an error-first callback, there are two scenarios you’ll need to handle:
On a successful response, the ‘err’ argument is null. Call the callback and include the successful data only.
On an unsuccessful response, the ‘err’ argument is set. Call the callback with an actual error object. The error should describe what happened and include enough information to tell the callback what went wrong. Data can still be returned in the other arguments as well, but generally the error is passed alone.
callback( new Error('Bad Request') );
That’s it! Easy, right?
Implementing an Error-First Callback
1 2 3 4
Above you have an error-first callback in action.
readFile takes in a file path to read, and calls your callback once it has finished. If all goes well, the file data is returned in the
data argument. But if somethings goes wrong,
readFile won’t have any data to return. In that case, the first argument will be populated with an error.
1 2 3 4
It’s up to you to check for that error, and handle it as best it can. The error object will usually contain some extra information describing what went wrong, which you can use to detect and safely handle all possible problems.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Unfortunately the information isn’t normally as explicit as
.fileNotFound, but handling errors properly is always worth the investment. Ignoring an error entirely can be disastrous for your application. Don’t cut corners on this.
Err-ception: Propagating Your Errors
The error-first pattern is the backbone for safe communication between Node.js modules. When a method passes its errors to a callback, it no longer has to make assumptions on how an error should be handled. Instead of choosing between throwing or ignoring, errors get handled at the level that makes sense.
When you’re consistent with this pattern, errors can be propagated up as as many times as you’d like. This is especially important when throwing isn’t possible, like within a web application. If some method threw an error while attempting to read a file the whole server would crash. By propagating it up the callback has a chance to handle the error. And if that specific route doesn’t know to handle it, it can propagate up even farther to be handled with a 500 or 404 error page.
1 2 3 4 5 6 7 8
Slow Your Roll, Control Your Flow
With a solid callback protocol in hand, there are endless possibilities for controlling your asynchronous flow. There are parallel flows, waterfall flows, and many more creative ways to direct asynchronous code. If you want to read in 10 different files, or make 100 different API calls, you no longer have to make them one at a time.
The async library can handle all this and more. And because it uses error-first callbacks, it’s incredibly easy to work with. Async can simplify and speed up your code, and hopefully keep you out of callback hell….
Bringing it all Together
To see all these concepts come together, check out some more examples on Github. And of course, you can always choose to ignore all of this callback stuff and go fall in love with promises… but that’s a whole other post entirely :)