Error "detail"

I'm making another pitch to improve Error now that proposal-error-cause has got to stage 4 and been archived. Another feature it would be great to add to Error natively - the ability to attach extra information to an error. Hypothetical usage example after this proposal:

throw new Error('Resource not found', {
  detail: {
    table: 'users',
    id: 'abc123',
    hint: 'Try calling createUser first',
    advice: 'Go outside more',
  }
})

The values in detail would just hang off the Error object, similar to if you did Object.assign(new Error('Resource not found', { detail: ... }).

This is similar to info in verror (which is now unmaintained).

It's better than using Object.assign, because it's standardised, so 1) the intent is clearer, 2) it's less weird - code-reviewers and linting tools won't object, 3) other tools like Sentry can usefully render the details, and can more reliably group errors based on the message (and, if desired, properties like detail.table).

It would be nice to have an official home for extra properties, as opposed to just sticking them on the error instance wherever we please. It means we don't have to worry about JavaScript adding a new built in property with the same name as what we were using, which, if happens might (might...) not break your code, but it is still confusing to all-or-a-sudden be accidentally shadowing built in properties.

1 Like

@theScottyJam I'm not sure if you're saying you like this idea or not? Do you mean going ahead with this would risk unexpected behaviour for existing code that does Object.assign(new Error(''), {detail: 123})? To me it seems pretty in line with proposal-error-cause.

Re the official home for extra properties, this was actually why I opened the PR which moved proposal-error-cause to an "options bag": Switch to options bag by mmkal ยท Pull Request #25 ยท tc39/proposal-error-cause ยท GitHub - I was thinking the second argument passed to the Error constructor would be that home.

I'm saying that I like this idea.

As things are today, I have to modify an error instance to add extra data to it, and then hope that tc39 doesn't later standardize and new property with the exact same name that I'm already using. If there was a new standard place for my arbitrary data to go (like you're suggesting), I wouldn't have to worry about that problem. It also just feels cleaner.

1 Like

Why isn't that place already "cause"? You can put an object there with whatever you want in it.

@ljharb because cause has a different semantic meaning than detail. My assumption was that usually cause will be an Error in the same way that usually something that's thrown is an Error. Not a requirement, but your Sentry instance is going to be more helpful if you stick with Errors.

Taking the example from my original post, but (ab)using cause:

It seems silly, would we really want to encourage writing code like that?

1 Like

I'm not sure why it's any better or worse to spell that property "cause" or "detail".

because cause has a different semantic

I think this is the key in @mmkal's point.

The error cause proposal proposal explains its usefulness by first stating that the pattern of "Catching an error and throwing [errors] with additional contextual data is a common approach of error handling pattern.", followed by explaining how there's no standard way to follow that pattern - thus, error causes are the standard provided for us, and the reason they exist is because they are "greatly helpful [for] diagnosing unexpected exceptions"

If the example error that @mmkal is describing got through to my program, I would (wrongly) assume that what happened was that an object literal got thrown, caught, and then put into a new error as the cause, then finally the new error got thrown. Because, semantically, that's what an error cause is supposed to mean. I certainly wouldn't expect a library author to be purposefully putting data related to the error in this "cause" field for my program to grab and use - especially since that data isn't the "cause" of the error, it's just additional information about the error.

1 Like

For these I think a subclass would satisfy these requirements, and a pattern I've seen used many times.

class ResourceError extends Error {
  constructor(message, { table, id, hint, advise...rest}) {
    super(message, ...rest);
    this.resource = { table, id };
    this.display = { hint, advise };
  }
}

This sounds like the more valuable part. Does sentry currently only inspect the message field?

1 Like

Subclassing isn't always the best option (though it is how I solve the problem today). Say I had subclasses the error class and added a new property called "cause". Then, whoops, tc39 decided to make that a standard property, and now anyone seeing an error with a "cause" property will assume certain semantics about it that may not have been true with my particular error.

A solution to that is always using either mutable own properties or getter/setter pairs for new proposals.

Even the error stack proposal includes a stack setter.

It's more about the conceptual conflict.

Similar to how Angular used to have a function named async, then later JavaScript added the async keyword, and while Angular's async function technically still worked, they still chose to deprecate it in favor of a new function that was exactly the same, but had a different name.

In a similar way, sure it might be possible to add conflicting properties to the base error class without technically breaking my code, but it makes my code really confusing.