Athan Clark
Athan Clark

Reputation: 3980

Node.js Error throwing known as bad practice, yet essential for TDD

I've heard from plenty of people saying that throwing errors in Node is bad practice, and you should rather manually handle them via CommonJS's callback syntax:

somethingThatPassesAnError( function(err, value) {
    if (err) console.log("ERROR: " + err);
});

Yet, I've found in multiple unit testing frameworks (Mocha, Should.js, Gently) that it seems like they want you to throw an error when something happens. I mean, sure, you can design your tests to check for equality of variables and check for not-null in error vars, but in the words of Ryan Dahl himself, "you should write your framework to make the right things easy to do and the wrong things hard to do".

So what gives? Can anyone explain why that practice exists? Should I start throwing fatal exceptions like require() would if the module couldn't be found?

Upvotes: 3

Views: 2185

Answers (4)

baetheus
baetheus

Reputation: 774

I tend to follow the guidance on Joyent's Error Handling in Node.js for this one. The oversimplified gist is that there are really two types of errors (operational and programmer), and three ways to pass errors (emitting an error event on an event emitter, returning the callback with the error argument as non-null, and throwing the error).

Operational errors are errors that you expect might happen and are able to handle, ie. not necessarily bugs. Programmer errors are errors in the code itself. If you are writing code and you expect the error, then any of the patterns for passing an error are valuable. For example:

  • If the error happens inside an asynchronous function that accepts a callback, using the idiomatic return callback(new Error('Yadda yadda yadda')) is a correct solution (if you can't handle the error in the function).
  • If the error happens inside of a synchronous function and is a breaking problem (ie. the program cannot continue without the operation that was attempted) then blowing up with an uncaught thrown error is acceptable.
  • If the error occurs in a synchronous function but can be dealt with, then the error should be dealt with, otherwise it should be thrown, and maybe the parent function can handle it, maybe not.

Personally, I tend to only throw errors that I consider fatal, thus my code is mostly devoid of try/catch blocks (I even wrap JSON.parse in a function defined thusly: function jsonParseAsync(json, cb) { var out, err; try { out = JSON.parse(json) } catch(e) { err = e }; return cb(err, out); } ). I also try to avoid promises because they conflate promise rejection and thrown errors (even though this is getting harder to do as promises become more ubiquitous). Instead, I tend to think of synchronous functions as mathematical proofs in that if they are correct they must always be correct (thus an error in a synchronous function should break the whole program, otherwise the proof can be wrong but still usable). My error creation and checks are almost entirely assertions for bad input and emitting error events or handling asynchronous errors idiomatically.

Upvotes: 1

numbers1311407
numbers1311407

Reputation: 34072

It because nodejs programs typically make heavy use of async, and as a result errors are often thrown after your try/catch has already completed successfully. Consider this contrived example.

function foo(callback) {
  process.nextTick(function() {
    if (something) throw "error";
    callback("data");
  });
}

try {
  foo(function(data) {
    dosomething(data);
  });
} catch (e) {
  // "error" will not be caught here, as this code will have been executed
  // before the callback returns.
}

The typical node pattern, of the first argument in a callback being an error, obviates this problem, providing a consistent way to return errors from asynchronous code.

function foo(callback) {
  process.nextTick(function() {
    if (something) return callback("error");
    callback("data");
  });
}

foo(function(error, data) {
  if (error) return handleError(error);
  dosomething(data);
});

Upvotes: 3

autistic
autistic

Reputation: 15642

I would suggest using exceptions to handle critical errors, much like the way require() works. If this functionality causes Node.js to misbehave, then that's a bug which I'm sure will get fixed in time.

Upvotes: 0

Brad
Brad

Reputation: 163478

It is my understanding that the case against throwing exceptions in JavaScript is due to the heavy use of asynchronous patterns. When an error occurs on another stack, you can't catch it. In those cases, use the err parameter as the first parameter for the callback.

I don't think that is the same as saying "never throw anything". If I have synchronous code, and an exception occurs, I throw it. There are differing opinions, but if callbacks aren't involved at all, I see no reason to not use throw.

Upvotes: 2

Related Questions