hello.

Node.js Handbook - Understanding Error-First Callbacks

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 scalable 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 your 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 (also known as an “errorback”, “errback”, or “node-style callback”) was first 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.

Why Standardize?

Node’s heavy use of callbacks dates back to a style of programming older than Javascript itself. Continuation-Passing Style (CPS) is the old-school name for how Node.js uses callbacks today. In CPS, a “continuation” function is passed as an argument to be called once the rest of the code has been run. This allows for different functions to safely pass control asynchronously back and forth across an application.

Almost all Node.js code follows this style, so having a dependable callback pattern is crucial. Without one, developers would be stuck maintaining different signatures and styles for 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:

  1. 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.
    Example: function(err, data)

When it’s time to call an error-first callback, there are two scenarios you’ll need to handle:

  1. On a successful response, the ‘err’ argument is null. Call the callback and include the successful data only.
    Example: callback(null, returnData);

  2. 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.
    Example: callback( new Error('Bad Request') );

That’s it! Easy, right?

Implementing an Error-First Callback

1
2
3
4
fs.readFile('/foo.txt', function(err, data) {
  // File Returned
  console.log(data);
});

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
5
6
7
fs.readFile('/foo.txt', function(err, data) {
  if (err) {
      console.log('Ahh! An Error!');
      return;
  }
  console.log(data);
});

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
fs.readFile('/foo.txt', function(err, data) {
  if(err) {
      if(err.fileNotFound) {
          console.log('File Doesn't Exist');
         return;
     }   
     if(err.noPermission) {
         console.log('No Permission');
         return;
     }
     console.log('Unknown Error');
      return;
  }
  console.log(data);
});

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
if(err) {
  // Handle "Not Found"
  if(err.fileNotFound) return res.send('File Does not Exist');
  // Handle "No Permission"
  if(err.noPermission) return res.send('No Permission');
  // Propogate Other Errors, Express Will Catch Them
  return next(err);
}

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

Enjoy this post? Node.js Handbook is a new series teaching beautiful design and maintainable architecture for Node.js applications.
Sign up to get new lessons delivered right to your inbox.
comments powered by Disqus