Skip to content

Error.name should not contain display elements #20253

Closed

Description

@Trott
  • Version: 10.0.0-pre (master)
  • Platform: n/a
  • Subsystem: error

Remainder of this is a comment by @felixfbecker in #13937 (comment) that I think deserves its own issue:

I agree that adding the code to the name is weird. For me name is a programmatically-inspectable property, just like code, and not for "human consumption" like message. Having pretty formatting in there with a space and brackets goes against that.
Browser errors have no code property, they always use name, so that's where this intuition is coming from. For example, for fetch you would have to check if err.name === 'AbortError'. The only other option is using instanceof, but that is bad practice (assert on interfaces, not implementations).

Personally I see code as a subcategory of name, e.g. name can be TypeError and code ERR_SOCKET_BAD_TYPE. It's both useful to be able to make assertions about the broader category of and error as well as the exact error reason. For example, in an Express error handler, I might want to never return any TypeErrors to the client because they are programming errors. Or take p-retry as an example (emphasis mine):

Returns a Promise that is fulfilled when calling input returns a fulfilled promise. If calling input returns a rejected promise, input is called again until the max retries are reached, it then rejects with the last rejection reason.
It doesn't retry on TypeError as that's a user error.

Checking code here wouldn't be helpful.

I would prefer if both name and code are for programmatic inspection without fancy formatting, and Error.prototype.toString() could be modified to include both error.name and error.code in the output, as toString() is unarguably purely for human consumption. Doing this for all errors, including user errors, would be a great feature and encourage users to make use of error codes too. Alternatively it could only be an internal base error class.

Activity

  1. BridgeAR commented on May 15, 2018

    @BridgeAR
    Member

    I just had a look at this and played around how this could be fixed and it turns out to be quite a difficult task at hand.

    We use util.inspect for everything that we log and when inspecting errors, we return the error stack (plus enumerable properties on the error).

    Manipulating the stack is not an option because the stack itself is also manipulated throughout the code after creating the error with e.g., Error.captureStackTrace. So manipulating error.toString() does not work (also for another reason: even if adding a specific toString() to each error it does not mean that this function would ever be called as we could likely use Error.prototype.toString.call(error) instead).

    I guess the best way to solve this is to just adding the properties that we want to show directly on the error as enumerable properties. That way these properties would always be visible when inspecting them (no matter if in the repl or elsewhere).

  2. BridgeAR commented on May 15, 2018

    @BridgeAR
    Member

    The repl would probably need some extra love but that should be fine.

    Here an example how it would look like when changing the name and printing code as well:

    { TypeError: The "fd" argument must be of type number. Received type undefined
        at Object.fs.write (fs.js:630:3)
        at repl:1:17
        at Script.runInThisContext (vm.js:89:20) code: 'ERR_INVALID_ARG_TYPE' }
    
  3. felixfbecker commented on May 15, 2018

    @felixfbecker
    Contributor

    So manipulating error.toString() does not work (also for another reason: even if add a specific toString()

    I don't understand why - wouldn't it result in the same output that is output right now in v10 with the modified name?

    In any case, I think going through util.inspect() is better because it can print name and code in an easy to understand way (that these are properties I can introspect).
    Maybe there could be some work on util.inspect() to not jam code on the last line of the stack trace to make it more visible.

  4. BridgeAR commented on May 15, 2018

    @BridgeAR
    Member

    @felixfbecker toString() is never called when inspecting or logging (that uses inspect internally). That is the reason why it would not work. And if you add a individual toString() function and use the prototype toString function, it would still not be called. As in:

    const err = new Error('foo')
    err.toString = () => console.log('called')
    Error.prototype.toString.call(err)
    > 'Error: foo'
    err == 123
    > called
    > false
  5. BridgeAR commented on May 15, 2018

    @BridgeAR
    Member

    Maybe there could be some work on util.inspect() to not jam code on the last line of the stack trace to make it more visible.

    👍 I am going to look into it.

  6. Trott commented on Jun 3, 2018

    @Trott
    MemberAuthor

    This ship seems to have sailed, unfortunately. Closing.

  7. felixfbecker commented on Jun 4, 2018

    @felixfbecker
    Contributor

    @Trott can't this be considered for Node 11? I would hate to have to live with this mistake for the rest of Node's life. Since name is currently a human-readable display property, changing it back to a programmatically-inspectable one would be a low-impact change (all code that was changed to not rely on name anymore would continue to work).

  8. Trott commented on Jun 5, 2018

    @Trott
    MemberAuthor

    This needs more attention, but there's no errors team. @BridgeAR @joyeecheung @jasnell

  9. reopened this on Jun 5, 2018
  10. added this to the milestone on Jun 5, 2018
  11. BridgeAR commented on Jun 5, 2018

    @BridgeAR
    Member

    As I tried to point out above this is not an easy task to solve. I do not know of any solution that allows exactly what we want. That was also the reason why @jasnell originally implemented it the way it is.

    After my recent changes all extra properties will be visible by default. That was not the case so far but it should already improve the situation.

    I am not sure anymore if I already checked the following: when accessing the error stack (or calling toString if there is no stack) we could manipulate the output string to add the name. This would likely work in most cases. This would probably be fine since almost everyone would also either call console.log or util.inspect on it and that would result in what we want. However, if someone accesses the stack directly, that information will be lost. The same if someone uses another inspection module than util.inspect. I am somewhat on the edge about that and would like others to decide if this would be "sufficient" or not. Anything else would probably not work at all - at least I can not think of any other solution.

  12. BridgeAR commented on Aug 28, 2018

    @BridgeAR
    Member

    I might have found a solution. Trying it out right now.

  13. BridgeAR commented on Aug 29, 2018

    @BridgeAR
    Member

    I tried it out and my approach works relatively well in most cases but as I feared above not in all cases. Therefore I still see no other way.

  14. jasnell commented on Oct 3, 2018

    @jasnell
    Member

    what's the current status on this? Will this be a semver major or no?

  15. BridgeAR commented on Oct 3, 2018

    @BridgeAR
    Member

    @jasnell there was no PR that changed the behavior.

    And I am not sure how we can address this properly. The only working way for me was to set the name in the constructor, access the stack trace (so it generates the one with the code) and reset the name to the proper one without code. This works relatively well. However, in case the application exits due to the error the stack is generated again (seems like C++ code does that). Therefore the error code is not visible in those cases anymore. If someone knows how to prevent the stack trace from being generated again, we could try this workaround.

  16. joyeecheung commented on Oct 3, 2018

    @joyeecheung
    Member

    Why do we have to keep the error code in err.name in the first place? Can't we just put it in err.message (after the colon)? I doubt if anybody is relying on any of these two being exactly the same as before since the whole point of these errors is to rely on err.code instead, so I don't think we would break anybody if we just do a semver-major bump and change err.name as well as err.message?

  17. joyeecheung commented on Oct 3, 2018

    @joyeecheung
    Member

    Also, formatting err.name with Name [${err.code}] seems to break Web compatibility in our Web API implementations (at least it breaks WPT and has to be worked around with #22556).

    This was brought up in #11299 @jasnell

    The only significant difference is the addition of the code property which would just need to be documented as a Node.js specific extension. instanceof Error / instanceof TypeError still work for these as expected.

    But WPT does not use instanceof because exceptions on the Web can come from different realms (so do Node.js exceptions thrown from different vms), and in addition to err.code, err.name is currently another significant difference from the simple exceptions.

    (So..maybe #11299 should be reopened?)

  18. BridgeAR commented on Oct 3, 2018

    @BridgeAR
    Member

    I would also prefer to have a "clean" name property instead of adding the code to it but it's not a strong opinion.

  19. joyeecheung commented on Oct 3, 2018

    @joyeecheung
    Member

    If we want to move the code from err.name to err.message, should we do this before v11, or should we wait and do this in the v11 cycle (pre-v12)? (I prefer to do this before v11 is cut, assuming we manage to revamp our tests in time, but I'll defer to @jasnell )

    (Also, we can use help from the upcoming code-and-learn to fix the Error [CODE] in tests, currently there are 200+ instances in 68 files)

  20. Trott commented on Oct 3, 2018

    @Trott
    MemberAuthor

    (Also, we can use help from the upcoming code-and-learn to fix the Error [CODE] in tests, currently there are 200+ instances in 68 files)

    Do we know what we'd want it replaced with? Checking err.code for sure, but also.... instanceof check on the original Error type (RangeError, TypeError, etc.)? Or something else?

  21. joyeecheung commented on Oct 3, 2018

    @joyeecheung
    Member

    @Trott common.expectsError({type: TypeError, code: 'ERR_CODE'}) as what we used to do. We can change common.expectsError to check err.name matches type.constructor.name instead of using instanceof later or now (that's what the WPT harness does in essence to work around errors in different realms)

  22. Trott commented on Oct 3, 2018

    @Trott
    MemberAuthor

    @Trott common.expectsError({type: TypeError, code: 'ERR_CODE'}) as what we used to do.

    I can live with that. Ideally, I would prefer to avoid the magic/special type property in common.expectsError() because it won't be available when we move (back) to assert.throws() (which is what I believe @BridgeAR and I have both been imagining we'd do after 8.x is end-of-life).

    EDIT: Although I'm sure we can figure out some way to tell assert.throws() to check it. assert.throws(block, object, options)? This is getting off-topic for this issue. I'll have this conversation elsewhere... :-D

  23. jasnell commented on Oct 3, 2018

    @jasnell
    Member

    I'm strongly -1 on moving the code to err.message. There is far more code out in the wild that parses through the error message than pays attention to the error name and moving the code to the message carries a very high risk of breaking code, which would force us to treat changing error messages as semver-major again, which takes us in the wrong direction. Including the code in the name was not a snap decision, it was well thought out as being the least disruptive approach.

    What I do think is a viable approach long term is to make a proposal to TC-39 to add code as a standard property along with a specification that ensures it's value is always serialized with the error message and stack.

  24. jasnell commented on Oct 3, 2018

    @jasnell
    Member

    I'm going to take this issue off the 11.0.0 milestone as it would require (a) having a PR, (b) having consensus on the approach, and (c) having TSC sign off to get pulled in to 11 at this point.

  25. removed this from the milestone on Oct 3, 2018
  26. joyeecheung commented on Oct 3, 2018

    @joyeecheung
    Member

    There is far more code out in the wild that parses through the error message than pays attention to the error name and moving the code to the message carries a very high risk of breaking code, which would force us to treat changing error messages as semver-major again

    If we still cannot move err.code out of err.name because people parse err.message (in the stacktrace or just as err.message), what difference does it make to have a err.code or not? Aren't we still stuck at the semver-major situation if we cannot move err.code out of err.name simply because that will change err.message/the stacktrace?

    It's hard to imagine that someone would rely on Error [ERR_CODE] being exactly the same format knowing that ERR_CODE is already available as err.code and is much more reliable (at least the format of error.name is not documented while error.code is in https://nodejs.org/api/errors.html). How would the code relying on this behavior look like?

    const match = err.name.match(/(\w+) \[(.+)\]/);
    //or
    const match = err.stack.match(/(\w+) \[(.+)\]:/);
    const [ _, name, code] = match;

    instead of

    const {name, code} = err;

    ?

  27. joyeecheung commented on Oct 3, 2018

    @joyeecheung
    Member

    @jasnell IIUC, you might be talking about places like

    const match = err.message.match(someRe);
    // expects there is no `[ERR_CODE]` suddenly showing up at the start

    ? But I believe for errors like those we don't even touch the err.name anyway? (e.g. uvException, errnoException, .etc) They are just the most simple Error with "Error" as name right now, so the change does not effect them anyway. The errors that do have their names formatted as Error [ERR_CODE] (i.e. the errors formatted with the E() function in internal/errors.js ) are currently all free to change their messages without going semver-major, so adding [ERR_CODE] to their error message doesn't change our policy.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    errorsIssues and PRs related to JavaScript errors originating in Node.js core.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions