Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the best way to expose and compose errors in Node.js?

I'm writing a module that talks to a REST API, and since the REST API provides nice, semantic error responses (e.g. 403 vs. 503), I want to convey those semantic errors to callers.

(Edit: what I mean by that is, the caller should be able to programmatically understand the cause of the error and act accordingly, e.g. display the appropriate UI.)

What's the best way for me to do so?

  1. Create my own Error subclasses for those semantics, e.g. mymodule.ForbiddenError, mymodule.ServiceUnavailableError? Callers would then check instanceof to derive semantics. This is most typical in statically-typed languages like C# and Java.

  2. Add e.g. a mymoduleCode property to standard Error instances, with semantic strings like 'Forbidden' or 'ServiceUnavailable'. Node.js itself does this, e.g. code: 'ECONNREFUSED'.

  3. Some other way?

==

I'm writing another module now, that wraps the first module. I don't want to expose the internal module's errors directly, but it'd be nice to compose/wrap them, for debuggability.

What's again the best way for me to do so?

  1. Add e.g. an internalError property to my Error instances that references the internal module's Error instance. Again C# and Java do this. (Exception#InnerException / Throwable#cause)

  2. Some other way?

Most tooling I've seen, though, only displays the stack property of Error instances, so this data would get lost in those cases. Is there a typical/conventional way that already exists?

like image 540
Aseem Kishore Avatar asked Nov 26 '13 19:11

Aseem Kishore


4 Answers

The use case you describe is one that is definitely underserved by Node right now. Inasmuch as there are conventions in use, they tend to be some mixture of:

  1. Creating new error types that simply derive from the Error constructor. Useful mostly for creating high-level categories of errors. You can use instanceof to discriminiate between types of errors, but that doesn't feel very JavaScripty.
  2. Creating Java-style Error subtypes that take another error as a constructor argument and then wrap them. If you make the subtype expose an API, you're basically using polymorphism instead of direct dispatch to handle the errors based on types.
  3. Creating semantically useful error messages via the Error constructor. This is a pattern I tend to use a lot.
  4. Attaching properties to Errors, which is also extremely common.
  5. Create a decoupled error-handling API that can be invoked from wherever the error originates. This gives you a single place to concentrate all the code that figures out what kind of error you have and what you should do with it, and inject whatever dependencies you need to handle the errors.

The real problem here is that these are all ad hoc mechanisms and I wouldn't say that any of them have reached critical mass. I think this is in part due to the fact that the combination of asynchronicity and JavaScript makes it tough to correctly and completely handle errors, so the recommendation is generally to shut down the whole process after capturing information about the error.

This doesn't do a great job of dealing with expected errors, but I think most of those errors are either captured by the framework (e.g. Express error handlers) or put into the Node callback convention. In short, I think there's room here to define some conventions, because the state of the art isn't that artful.

like image 191
othiym23 Avatar answered Oct 18 '22 05:10

othiym23


Found this great article that talks about this:

http://www.joyent.com/developers/node/design/errors

Too much to paste here, but under "Specific recommendations", items 2 through 5 address this:

  1. Be clear about what your function does.
  2. Use Error objects (or subclasses) for all errors, and implement the Error contract.
  3. Use the Error's name property to distinguish errors programmatically.
  4. Augment the Error object with properties that explain details
  5. If you pass a lower-level error to your caller, consider wrapping it instead.

And in particular, the article links to this module for wrapping/composing errors:

https://github.com/joyent/node-verror

I'm not sure I'll necessary follow these exact conventions — e.g. I see value in having an application-domain code property — but the principles are very helpful.

like image 37
Aseem Kishore Avatar answered Oct 18 '22 03:10

Aseem Kishore


Edit I'm new to StackOverflow and Node.js. Someone else should answer this question! :-)

If you're coming from Java/C# welcome to JavaScript! I've never seen a Node.js library use instanceof to check for error types but as you've stated is pretty common in static languages.

With that said, it's typical to just create new errors, and callback with those using NodeJS's style of callbacks (err, response).

I run into error messages from other modules all the time, and it's helpful to know where they came from vs. creating wrapped error messages that may hide where it actually died on me (requiring more on my part to dig around).

An example of creating a function that could handle rest error messages that are strings (or in your case, invalid response):

function myFunction(callback) {
    callRestAPI('http://someApi.com/request', function(errorString, jsonResponse) {
        if (errorString) { 
            callback(new Error("something wrong in myFunction! " + errorString)); 
        } else {
            callback(null, JSON.parse(jsonResponse));
        }
    });
}

In your case the "errorString" check would basically be the response (403 / 503) and you can structure your error message to be sent into new Error( ... ), e.g. new Error('Failed! Got response 403!')

I may have missed your point, maybe someone else can be more thorough.


After a re-read, can you post what module you are wrapping. Is it node-request?

like image 34
Glareo Pensy Avatar answered Oct 18 '22 05:10

Glareo Pensy


I'm pretty new to node as well, so take this with a grain of salt, but I'd go with your #2 option, adding a property. (This is also suggested here).

This is pretty much your original #2 option. If the error handlers just log the stack, not the error itself, that's their fault. :-) (seriously, not sure how you can do any better)

like image 1
user949300 Avatar answered Oct 18 '22 04:10

user949300