On 16 May 2015 at 18:35:15, Owen Allen ([email protected]) wrote:

There's a lot of blog posts around the internet talking about the mechanics of 
handling errors, be it domains or process.on("uncaughtException"). I can't find 
much definitive talk about the philosophy of error handling though. 
Specifically when should a function return an error cb(err), or return an error 
state such as cb(null, { success : false, message : "Some useful message", resp 
: httpResponse }).

In example, lets imagine we're creating a social media NPM package which has an 
async function which queries a social media API via an HTTP call using the 
popular request module and returns some posts. Our function takes 3 arguments a 
userid and a count and a callback. There are a variety of exit scenarios for 
this function:
So I've responded with what I think.



1. Malformed arguments sent to the function such as a missing cb or a 
null/undefined/incorrectly formatted userid.
Throw -- use a library like aproba or otherwise validate args synchronously.

2. Unable to reach the service via HTTP.
Call back with error.

3. Service returned a non-200 status code for some reason, maybe we're being 
rate-limited.
If a library that exposes HTTP semantics, call back with no error but object 
with status.

If a library that hides HTTP semantics, call back with error.

It will really depend on what layer you're trying to expose; mixing them 
usually leads to confusing APIs. Clarifying what layer you are targeting can 
really help clarify how the API should work.

4. Reached the service, but the call failed at the service due to invalid 
parameters, some services return a 200 status code for this type of failure 
other's return a non-200 status code.
Services that report errors with 200 are awful. Fix that up as if the service 
behaves.

If it's a protocol concept that's exposed, call back with status object. If 
you're wrapping the protocol so the details aren't exposed, call back with 
error.

5. Reached the service, the call succeeded but returned an empty array of posts.
Empty success is still success. Call back with the empty array.

6. Reached the service, the call succeeded and returned data.
Call back with it.

7. The function throws an unhandled error due to programmer mistake.
Bummer. Catch that internally and call back with it. An async throw due to that 
kind of mistake in a callback is awful and crashes the whole process 
unrecoverably. This is the worst side-effect of error handling in node.

 Static analysis to find cases, remove the catch when you get it right.

Usually my philosophy for handling errors in this case would be:

1. cb(new Error("Invalid parameters, must pass a callback"));
2. cb(err); // the error that was returned by the http request
3. cb(null, { success : false, message : "Service returned a " + 
resp.statusCode + " status code", resp : resp });
4. cb(null, { success : false, message : serviceResponse.message, resp : resp 
});
5. cb(null, { success : true, data : [] });
6. cb(null, { success : true, data : posts });
7. Whoops, let error bubble up to the application employing our package and it 
will handle it or crash.
In general I find the success true/false to be a bad pattern: Either expose the 
protocol _and all the details_ for it, or don't, and hide them completely into 
the error path as debugging data, but don't expect the users of your API to act 
on that information.

I attach code properties to errors where I can. Use the VError class to attach 
context to inner exceptions, and to make formatting error messages easier with 
format strings.

That's just one way to approach the problem. It could be argued that cases 1 - 
4 should all cb an error and that the error object should be packed with the 
additional data to help debug or be a custom error type. Yet at the same time 
some people argue it's a bad idea to pack additional non-standard keys on to 
Error objects because it results in inconsistency between Error objects and 
modules. If every npm module packed it's Error objects with custom keys 
wouldn't we end up in a pretty chaotic situation? In addition, if that error is 
thrown or console.error() those extra keys will not show up because of the 
native behavior of Error.prototype.toString(). 
Use the code property for consistent, machine-matchable tokens on errors. 
Custom subclasses are just annoying to define and give little useful 
information that a code property won't. Use the message for human readable 
message.

Yet another approach would be for 1 - 4 to return an error AND the additional 
data. Such as cb(new Error(serviceResp.message), { success : false, message : 
serviceResp.message, resp : resp }). This way downstream modules have the 
useful debugging information available, but we still end up treading down the 
error pathway of control flow structures such as async and promises. In 
example, in async if an error is returned it short-circuits all the way to the 
end.
I rarely see that go well -- sometimes it's clever and nice, but generally, you 
don't need it, and it's only confusing. And it's not particularly composable, 
in that not all error handlers do this, and passing two parameters through a 
complex chain of functions is far more difficult to get right. You end up 
throwing away the extra data if it's not attached to the error object, so just 
do that instead.

Lots of this is subjective, or just plain "better if people tend to do it the 
same way", but I find this approach works for me.

Aria


-- 
Job board: http://jobs.nodejs.org/
New group rules: 
https://gist.github.com/othiym23/9886289#file-moderation-policy-md
Old group rules: 
https://github.com/joyent/node/wiki/Mailing-List-Posting-Guidelines
--- 
You received this message because you are subscribed to the Google Groups 
"nodejs" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To post to this group, send email to [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/nodejs/etPan.5557ca1b.1688145c.ddfd%40Corona.local.
For more options, visit https://groups.google.com/d/optout.

Reply via email to