Callback conventions in node.js, how and why

When first confronted with node.js, you are not only presented with a completely new programming environment. You also encounter what is often referred to as callback hell accompanied by weird unfamiliar programming patterns. One of these is the way node treats callback functions.

The following post explains the conventions that node.js uses for its callback patterns (referred to as Continuation-passing style) and how you should implement them in order to comply.

The first argument is an error object

Node expects - almost - all callback functions to accept an Error object as the first argument. If no error occurred, the first argument should be null. If you use inline anonymous functions, this is a typical code snippet that you will encounter using node:

There is a reason why this is a usefull pattern: Imagine, you have a chain of asynchroneous functions that were to execute one after the other. I.e. read a file, get something out of a database, write something into the database and output the results to a callback function.

Now think something went wrong in the first function, i.e. the file cannot be read. You don’t want to blindly execute the other functions next in line. Instead, you skip the other functions and directly return to your callback function and let it choose how to handle the error. When no error occurred, continue with the next step.

This pattern lets you waterfall through function chains and leaves handling the error it up to the original invoking function. It is likely that you do not want to throw uncaught errors all the time.

Pass error objects, not strings

While we’re at it, when you create errors, you should create actual Error objects that are passed around. When Error objects are created, the JavaScript engine inserts additional information into them (i.e. the stack trace, file name, line number) that you can be useful for debugging.

The last argument is the callback function

If your function expects a callback function as an argument, it should be the last argument. That callback function in turn should also accept an Error object or null as the first argument, as described above.

Note that if you do not need a callback function, i.e. when you just don’t perform asynchroneous actions, you don’t forcefully need to demand one.

Additional and optional arguments go in between

Any more arguments, required or optional, should go in between the error and the callback parameter. Below is an example how you could retrieve the optional arguments:

Note that there other ways to check whether the optional arguments were supplied. This is just a very broad pattern that you can reuse.

When to apply these pattern fully

You don’t always need to follow this pattern in detail. Use common sense to find out where to omit certain arguments.

  • When your function is the first in line of a longer chain of asynchroneous calls, it does not need to accept an Error object. The next in line should though.
  • When your function does not perform any asynchroneous calls and you can simply return your result, you don’t need to take a callback function.
  • If your function returns multiple arguments and may fail, even though it is not asynchroneous, you may very well feed them to a callback function instead of stuffing them into a return statement. This way your code is more readable - to node developers.

The more node.js style code and API’s you encounter, the more you will see that these patterns are used broadly among node libraries and modules and that it makes sense to use these.