Error "detail"

It's definitely a reasonable concern, you're not wrong at all - if you make a feature available, people will use it. And in this case in particular, it's worth remembering that not all environments will understand or expect the .details property, and like you say, Errors should not become useless because, in the worst-case scenario, some framework causes all Error.messages to become "An error was thrown by the application:". It's also a given that documentation is not a solution, we can't just say "let's tell people not to use details that way".

Put another way: right now, the lack of expressivity means everyone is using .message to communicate whatever needs to be described about any given error, because that's the only option. The corollary to that is that an Error consumer can be certain they've recorded whatever needs to be described just by storing the .message field.

So, if we want to make sure people don't abuse the details feature and, in so doing, break consumer expectations of the .message field, let's reduce its expressivity. My problem statement is that "Error should be as expressive as console.log", but this proposal currently goes further than that by expressing the details as an object with named fields.

So, let's restrict the detail property to be arrays only.

It still hasn't, technically, lost any expressivity, because you could just put an object with named fields as the single element of the details array, but that requires developer intent to break the standard. And that's fine, they should be able to, but it shouldn't be the norm. Having details be an array also means that placeholders become much simpler - they can follow regex replacement placeholders and be $0, $1, etc.

But there's another thing that restricting it to arrays buys us, and I'll show rather than tell:

const httpCode = 404, url = 'example.com';
throw Error.new`Received a ${httpCode} response while sending a request to ${url}`

// The Error.new() template function calls the following:

new Error("Received a $0 response while sending a request to $1", {
    detail: [httpCode, url],
});

// Which results in an Error object with the following properties:

{
    message: "Received a 404 response while sending a request to example.com",
    rawMessage: "Received a $0 response while sending a request to $1",
    detail: [404, "example.com"],
}

// and could be displayed in the same manner as:

console.error(
    "Received a ", httpCode, " response while sending a request to ", url);

I dunno about you but I love that syntax. And, being a static method, Error.new is polymorphic to subclasses, so you can use any MySpecializedError.new in the same way.

The placeholder replacement only takes place for array indices that are valid to the length of the detail array, so if the array is length 0 or omitted, no placeholders are replaced and the message property will be the same as the message parameter passed to the constructor, regardless of if there are any $0 sequences in the message.

If there are any valid placeholders, though, they will get replaced by the string coercion of that array value, the same as string templating and String.raw does. In that case, there will be a rawMessage property with the string passed as the message parameter to the constructor. I figure the Error prototype will have a get rawMessage() { return this.message; } getter defined, so that knowledgeable clients can always use it to get the raw value of the message property, whether placeholder replacement was performed or not.

What do you think? And, for that matter, @mmkal, what do you think about the reduction in scope of the feature, restricting the detail property to array values?

2 Likes