If Google’s V8 Engine is the heart of your Node.js application, then callbacks are its veins. They enable a balanced, non-blocking flow of asynchronous control across modules and applications. But for callbacks to work at scale you’ll needed a common, reliable protocol. The “error-first” callback (also known as an “errorback”, “errback”, or “node-style callback”) was introduced to solve this problem, and has since become the standard for Node.js callbacks. This post will define this pattern, its best practices, and exactly what makes it so powerful.
Node.js relies on asynchronous code to stay fast, so having a dependable callback pattern is crucial. Without one, developers would be stuck maintaining different signatures and styles between each and every module. The error-first pattern was introduced into Node 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 two rules for defining an error-first callback:
- The first argument of the callback is reserved for an error object. If an error occurred, it will be returned by the first
- The second argument of the callback is reserved for any successful response data. If no error occurred,
errwill be set to null and any successful data will be returned in the second argument.
Really… that’s it. Easy, right? Obviously there are some important best practices as well, but before we dig into those lets put together a real-life example with the basic method
fs.readFile() takes in a file path to read from, and calls your callback once it has finished. If all goes well, the file contents are returned in the
data argument. But if somethings goes wrong (the file doesn’t exist, permission is denied, etc) the first
err argument will be populated with an error object containing information about the problem.
Its up to you, the callback creator, to properly handle this error. You can throw if you want your entire application to shutdown. Or if you’re in the middle of some asynchronous flow you can propagate that error out to the next callback. The choice depends on both the situation and the desired behavior.
##Err-ception: Propagating Your Errors
When a function passes its errors to a callback it no longer has to make assumptions on how that error should be handled.
readFile() itself has no idea how severe a file read error is to your specific application. It could be expected, or it could be catastrophic. Instead of having to decide itself,
readFile() propagates it back for you to handle.
When you’re consistent with this pattern, errors can be propagated up as as many times as you’d like. Each callback can choose to ignore, handle, or propagate the error based on the information and context that exist at that level.
##Slow Your Roll, Control Your Flow
With a solid callback protocol in hand, you are no longer limited to using one callback at a time. Callbacks can be called in parallel, in a queue, in serial, or any other combination you can imagine. If you want to read in 10 different files, or make 100 different API calls, there’s no reason to make them one-at-a-time.
The async library is great for advanced callback usage. And because of the error-first callback pattern, it’s incredibly easy to hook in to.
##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 :)