These "Node-style" callbacks come with their own particular set of pitfalls.

Forgetting to propagate the error

As you could see, the error is sent to us as an argument, it's not at all an exception being thrown that propagates automatically along the call stack.

In a way that's to be expected: exceptions, as we discussed, are not meant for asynchronous code: they would propagate up the execution call stack, not the more useful scheduling call stack.

Still, this manual error propagation means we can simply ignore potential errors, effectively pushing them under the rug, and keep going with a program whose internal state is corrupted.  This may be an honest mistake, or the result of sheer laziness not bothering to handle or propagate the error at every asynchronous step in our processing…

Code example: 12-pitfall-losing-errors.js

Look at this code: in the callback for readdir(), we completely skip handling the potential error we receive, and go ahead calling our own callback as if everything were dandy, no matter what.
And if we scan an existing directory (demo run), everything does look good.

But if we scan an incorrect directory (demo run), instead of getting an error propagated to our callback by our API, which would allow us to react accordingly, we incorrectly get called in a success context, when the list of files is actually undefined, as it wasn't provided by the underlying readdir(). And an “invisible” error is hellish to debug.

Contradictory calls

A common variation on that theme is when you indeed propagate the error… but forget to stop there.

Invoking our callback does not automatically short-circuit our own function: it's not like throwing an exception!

It's up to us to manually short-circuit our own code once we propagated the error. Most of the time, a simple return will do.

Code example: 13-pitfall-both-outcomes.js

Look at this variant of our previous code: we do propagate the error, but forget to stop there.

If we list a valid directory, it's still good (demo run), but on an invalid directory, we get a weird outcome, with our main callback being invoked twice: once in error mode, hence the warning, and once more in success mode, with an undefined list of names!

You can bet that our callback was never designed to be called twice, especially in a contradictory fashion! This can wreck all sorts of mayhem in our program.

You can fix that by adding a return right after propagating the error. Technically, because callbacks of asynchronous processing usually don't have a return value, or at least we ignore these, you could combine the return with the invocation of the callback, but I find this code to be a bit misleading, or at least confusing, as it gives the false impression return values do exist and get used; I would advise you instead to use a separate return, after invoking the callback, to clear up any confusion.