Error objects in W3C APIs

# Jonas Sicking (11 years ago)

We have been debating how to design errors raised by future W3C specs to look like. This includes both synchronously and asynchronous APIs. I.e. both thrown exception objects, and asynchronously reported errors reported by for example Promises.

We'd like to get TC39s input on which direction to go.

A few of the goals that we've had are:

Use the same objects for both synchronous and asynchronous errors. This simplifies error handling, and allows errors to flow through both synchronous and asynchronous call chains.

The error objects should test true for |instanceof Error|. I.e. they should either be of type Error, or of a subclass of it.

We believe there are situations when it's useful to let code detect what type of error was produced. For example when doing a XMLHttpRequest, it's useful for the caller to be able to know if there was a network error, and it should tell the user to check if they are properly connected to the internet. Or if there was an authentication error and the user should try a different username/password. Or if there was some other error which the website should log and send back to the developer for further analysis.

Even in scenarios when a given API can only produce one type of error, it can be valuable to include specific code-readable information in that error. A call chain might call into several different functions that can fail, and it's convenient if you can place an error handler at the top of the call stack which handles errors from multiple different functions. This also applies for asynchronous errors where a chain of Promise.then() calls can surface errors from many different operations to a single error handler at the end of the chain.

In situations where it's useful for code to detect which type of error had happened we want to use the same mechanism as for normal JS errors, i.e. the .name property. We do not want to always set .name to something generic like "DOMException" and then add something like .subname. This seems to create a fork in the design where W3C specs behaves different from core JS. And it'd also force authors to check both .name and .subname which seems annoying.

It is a little unclear what backwards compatibility constraints that we have. Currently many specifications throw DOMExceptions with a .code and a .name property. The .code is set to a numeric value and the .name property uses the same style of naming as built-in errors. For some of these errors we likely have to make |error instanceof DOMException| test true, and .code return the values they currently return.

However we might have the flexibility to use whatever class we want, even for old errors. As long as the class is a subclass of DOMException and thus the instanceof test above tests true. Also, DOMException can very likely be made a subclass of Error.

And of course for new errors in specs that have not yet shipped we have full flexibility to do whatever we want.

So the question is, what should we do?

Option A) Use subclasses of DOMException where web compatibility requires, and direct subclasses of Error otherwise. So we'd add a InvalidStateError class which inherits DOMException. But a new FileIOError could inherit directly from Error. DOMException would inherit Error which means that all errors would still be instanceof Error.

Option B) Don't use more subclasses. Instead use DOMException or Error directly. But the .name would contain values like "InvalidStateError" or "FileIOError". DOMException would still inherit Error, which means that all errors would be instanceof Error.

Option C is "something else".

The advantage of A is that it follows the pattern of current JS errors. The disadvantage is that it clutters the global object with a lot of error constructor functions which doesn't really provide any value as far as I can tell.

Another advantage with A is that it gives us a prototype object to stick additional information for certain errors. For example the ConstraintError reported by IndexedDB might in the future provide information about which key there was a constraint validation in. Likewise a NetworkError might want to provide the response body of a 404 result.

Such information is possible even using option B, but requires that properties are added on the error object instances, rather than as a getter on the prototype (which is the pattern used by other properties on Error subclasses).

I'm personally in favor of option B. It seems simpler while still retaining the ability to check the error type through a single operation, i.e. by checking error.name.

But I'm curious to hear others' thoughts. Especially about if there's any good Option Cs.

/ Jonas

# Boris Zbarsky (11 years ago)

On 2/4/14 4:27 AM, Jonas Sicking wrote:

The advantage of A is that it follows the pattern of current JS errors. The disadvantage is that it clutters the global object with a lot of error constructor functions which doesn't really provide any value as far as I can tell.

Don't they allow easy creation of these new error types from script?

There is no nice way I can see, from script, to produce an instance of Error with .name set to something else. You have to new Error() and then explicitly set .name on the resulting object or something. So a typical pattern like:

throw new FooError("xyz");

becomes:

var err = new Error("xyz");
err.name = "FooError";
throw err;

Such information is possible even using option B, but requires that properties are added on the error object instances, rather than as a getter on the prototype (which is the pattern used by other properties on Error subclasses).

Adding the properties on the instances via the constructor is still nicer, as above...

I guess pages could basically define their own error constructors if they wanted to.

# Allen Wirfs-Brock (11 years ago)

We could consider respecifying Error.prototype.name such that:

class FooError extends Error{} 
console.log(new FooError("xyz").name); //outputs "FooError"

It would require changing Error.prototype.name from a data property to an accessor property that looks at the 'name' property of the this value's constructor. Probably something like:

get name() {
     var name = this.construtor.name.
     return name? name : "Error";
}
# Allen Wirfs-Brock (11 years ago)

On Feb 4, 2014, at 1:27 AM, Jonas Sicking wrote:

...

synchronous and asynchronous call chains.

The error objects should test true for |instanceof Error|. I.e. they should either be of type Error, or of a subclass of it.

However, the standard instanceof behavior isn't friendly to cross frame/realm usage. ES6 has a @@hasInstance hook that can be used to change that behavior but that would create an inconsistency between between the platform exception objects and the built-in language exception objects.

We believe there are situations when it's useful to let code detect what type of error was produced. For example when doing a XMLHttpRequest, it's useful for the caller to be able to know if there was a network error, and it should tell the user to check if they are properly connected to the internet. Or if there was an authentication error and the user should try a different username/password. Or if there was some other error which the website should log and send back to the developer for further analysis.

Even in scenarios when a given API can only produce one type of error, it can be valuable to include specific code-readable information in that error. A call chain might call into several different functions that can fail, and it's convenient if you can place an error handler at the top of the call stack which handles errors from multiple different functions. This also applies for asynchronous errors where a chain of Promise.then() calls can surface errors from many different operations to a single error handler at the end of the chain.

The arguments you are making above are really platform independent, and not just about the web platform. Essentially, you are arguing that the exception objects/conventions are inadequate for complex systems. Without taking sides on that issue, it seems like a valid issue for TC39 to consider and from a broader perspective than just supporting the browser. A strawman proposal on that topic seems like the starting point. It should be informed by browser requirements but also take into account other host and application requirements.

In situations where it's useful for code to detect which type of error had happened we want to use the same mechanism as for normal JS errors, i.e. the .name property. We do not want to always set .name to something generic like "DOMException" and then add something like .subname. This seems to create a fork in the design where W3C specs behaves different from core JS. And it'd also force authors to check both .name and .subname which seems annoying.

It is a little unclear what backwards compatibility constraints that we have. Currently many specifications throw DOMExceptions with a .code and a .name property. The .code is set to a numeric value and the .name property uses the same style of naming as built-in errors. For some of these errors we likely have to make |error instanceof DOMException| test true, and .code return the values they currently return.

There is no reason that direct instances (or subclass instances) of Error can have a 'code' property.

However we might have the flexibility to use whatever class we want, even for old errors. As long as the class is a subclass of DOMException and thus the instanceof test above tests true. Also, DOMException can very likely be made a subclass of Error.

The cross-frame instanceof issue...

And of course for new errors in specs that have not yet shipped we have full flexibility to do whatever we want.

So the question is, what should we do?

Option A) Use subclasses of DOMException where web compatibility requires, and direct subclasses of Error otherwise. So we'd add a InvalidStateError class which inherits DOMException. But a new FileIOError could inherit directly from Error. DOMException would inherit Error which means that all errors would still be instanceof Error.

+1

Option B) Don't use more subclasses. Instead use DOMException or Error directly. But the .name would contain values like "InvalidStateError" or "FileIOError". DOMException would still inherit Error, which means that all errors would be instanceof Error.

Option C is "something else".

The advantage of A is that it follows the pattern of current JS errors. The disadvantage is that it clutters the global object with a lot of error constructor functions which doesn't really provide any value as far as I can tell.

Think ES6 modules: import {FileIOError} from "DOMExceptions";

or as in interim solution:

window.DOMExceptions.FileIOError

Another advantage with A is that it gives us a prototype object to stick additional information for certain errors. For example the ConstraintError reported by IndexedDB might in the future provide information about which key there was a constraint validation in. Likewise a NetworkError might want to provide the response body of a 404 result.

+1

Such information is possible even using option B, but requires that properties are added on the error object instances, rather than as a getter on the prototype (which is the pattern used by other properties on Error subclasses).

I'm personally in favor of option B. It seems simpler while still retaining the ability to check the error type through a single operation, i.e. by checking error.name.

But I'm curious to hear others' thoughts. Especially about if there's any good Option Cs.

I've designed exception handling systems before and designed and worked with complex exception hierarchies. My main caveat is that they are somewhat of an attractive nuisance. It's easy to get into designing beautiful classification hierarchies of dozens of different kinds of exceptions, each with their own specific failure information that is captured. In practice, I've found very little actual utility for them. The more kinds of exceptions you have, the harder it is for developers to remember where each is used and how they differ from each other. If you built a deep classification hierarchy then developer have to try to remember its structure. And like any hierarchical organization, you quickly are tempted to want to allow for multiple parent (ie multiple inheritance) which ultimately would add even more seldom used complexity.

I do think this is an issue that TC39 should take up.

# Domenic Denicola (11 years ago)

From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Allen Wirfs-Brock <allen at wirfs-brock.com>

I've designed exception handling systems before and designed and worked with complex exception hierarchies. My main caveat is that they are somewhat of an attractive nuisance. It's easy to get into designing beautiful classification hierarchies of dozens of different kinds of exceptions, each with their own specific failure information that is captured. In practice, I've found very little actual utility for them.

My (more limited) experience is the same. However, I think the utility that the DOM specs try to capture with their errors is different than that captured by the exception hierarchy that one might think of when looking at them. Let me set the stage:

There seem to be a few possible properties of an exception that could be varied depending on the situation:

  • The type. Typically this is very broad in ES, and in the DOM even more so (everything is DOMException). In ES this is the same as the .name property, but not in the DOM.
  • The category. ES has no such concept, but DOM specs use the .name property for this, with a variety of generic categories like "InvalidStateException," "NetworkError," etc. 1
  • The human-readable message. This is never specified, from what I can see.
  • The proximate cause. By this I mean the exact situation or line of the relevant spec that caused the error to be thrown. Every such cause should be distinguishable, even if they belong to the same category. Usually there will be a one-to-one correspondence between human-readable messages (or message format strings, with appropriate placeholders) and machine-readable proximate causes. I've seen this rarely, but some parts of Node.js use error codes for this.

I have not seen much attention paid to the "proximate cause" idea in the wild. But I think it is perhaps the most important. Allen's point about hierarchies being largely useless resonates with me on the type and category level. That is, even the DOM's more fine-grained exception categorization is not that useful if I am trying to recover from a specific scenario. Whereas, the ability to specify specific situations to recover from would be much more fruitful.

For a great example of these levels, see recent discussions on the streams repo. We have two situations: writing to a closing stream, and writing to a closed stream. On an ES level, these would end up as both TypeErrors, as discussed, with no further distinguishing information besides implementation-defined message. Working on a DOM level would be no different, I think: both would likely become DOMExceptions with name of "InvalidStateError". What we really want is a machine-readable (and thus rigorously specifiable) "proximate cause" field that consumers could use to distinguish which situation they got themselves into, especially e.g. in logs. As-is, we just folded them both into TypeError with no detail on the cause, hoping implementations would be helpful about this.

All this said, I think the DOM's categorization, and my proximate cause idea, are both trying to allow you to distinguish failure modes. You could envision, especially with better syntax support, letting most errors bubble, but catching and handling NetworkErrors with a retry. It would be even better, I claim, to be able to handle "network error from no network" separately from "network error due to server misbehavior", thus the proximate cause idea. This divide, between categorization and proximate cause, is part of why I think the DOM's exception categories evoke semi-useless exception hierarchies, even if they are aiming at a useful goal.


As you can see, I Have Opinions on this. I also know a number of community members who do as well. I'd love to discuss this further and perhaps pick up on that strawman idea you are suggesting, Allen.

# Andrea Giammarchi (11 years ago)

you introduced already CustomEvent interface ... just go CustomError and add to both a name property ?


new CustomError('type', {
  name: 'WhateverIsUsedFor',
  bubble: false,
  detail: ifNeeded_Whatever
});

Pros:

  1. easy to polyfill
  2. works out of the box
  3. backward compatible

Cons:

  1. everyone in JS is implementing a different "event system" anyway so it won't be adopted by node.js and it won't probably be used in Promises neither since these decided to receive everything but events

My 2 cents

# Jonas Sicking (11 years ago)

On Tue, Feb 4, 2014 at 9:49 AM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Allen Wirfs-Brock <allen at wirfs-brock.com>

I've designed exception handling systems before and designed and worked with complex exception hierarchies. My main caveat is that they are somewhat of an attractive nuisance. It's easy to get into designing beautiful classification hierarchies of dozens of different kinds of exceptions, each with their own specific failure information that is captured. In practice, I've found very little actual utility for them.

My (more limited) experience is the same.

I agree. Creating hierarchies of error "types" seems fragile to me.

A better approach seems like having a short list of flat error types. In cases where it might be useful to have a more specific error type, instead include additional information on the error. So for example, rather than having a NetworkResourceNotFound error, use NetworkError but add a property which indicate what type of network failure. Not sure if that was what you were proposing with the "proximate cause"? More on that below.

I think the general direction that we've been heading lately has been pretty good. I.e. error types like NetworkError, QuotaExceededError are descriptive and have clear use cases for code to catch and handle.

On the flip side, the DOM also has some overlapping errors that are too specific. IndexSizeError should simply have been RangeError, no need to use a separate exception because it was an index that was out-of-range, rather than some other type of argument.

Then there's a bunch of errors which are unclear why you'd ever want to catch them at runtime. For example HierarchyRequestError, NotFoundError, NamespaceError and URLMismatchError is unclear to me why you'd ever want to catch. It seems useful to provide detailed information so that a developer is helped with understanding what went wrong, but that can be done through the .message property. TypeError is probably fine for many of these. Though I sort of hate its name and would be happy to see something more descriptive become available.

However, I think the utility that the DOM specs try to capture with their errors is different than that captured by the exception hierarchy that one might think of when looking at them. Let me set the stage:

There seem to be a few possible properties of an exception that could be varied depending on the situation:

  • The type. Typically this is very broad in ES, and in the DOM even more so (everything is DOMException). In ES this is the same as the .name property, but not in the DOM.
  • The category. ES has no such concept, but DOM specs use the .name property for this, with a variety of generic categories like "InvalidStateException," "NetworkError," etc. [1]

I'm not sure that there's a difference between these two. Or at least I don't think there's a difference between the stuff that the DOM puts in .name, and that ES puts in the classname. They express the same thing just in different ways.

But that might not be relevant to your proposed solution.

  • The human-readable message. This is never specified, from what I can see.

I don't think we should specify the exact string that goes into these messages. It's good if implementations are free to add additional details if they see developers struggling with something.

But we could definitely do with more recommendations for implementations about what information to include here.

  • The proximate cause. By this I mean the exact situation or line of the relevant spec that caused the error to be thrown. Every such cause should be distinguishable, even if they belong to the same category. Usually there will be a one-to-one correspondence between human-readable messages (or message format strings, with appropriate placeholders) and machine-readable proximate causes. I've seen this rarely, but some parts of Node.js use error codes for this.

I have not seen much attention paid to the "proximate cause" idea in the wild. But I think it is perhaps the most important. Allen's point about hierarchies being largely useless resonates with me on the type and category level. That is, even the DOM's more fine-grained exception categorization is not that useful if I am trying to recover from a specific scenario. Whereas, the ability to specify specific situations to recover from would be much more fruitful.

For a great example of these levels, see [recent discussions on the streams repo][2]. We have two situations: writing to a closing stream, and writing to a closed stream. On an ES level, these would end up as both TypeErrors, as [discussed][3], with no further distinguishing information besides implementation-defined message. Working on a DOM level would be no different, I think: both would likely become DOMExceptions with name of "InvalidStateError". What we really want is a machine-readable (and thus rigorously specifiable) "proximate cause" field that consumers could use to distinguish which situation they got themselves into, especially e.g. in logs. As-is, we just [folded them both into TypeError][4] with no detail on the cause, hoping implementations would be helpful about this.

All this said, I think the DOM's categorization, and my proximate cause idea, are both trying to allow you to distinguish failure modes. You could envision, especially [with better syntax support][5], letting most errors bubble, but catching and handling NetworkErrors with a retry. It would be even better, I claim, to be able to handle "network error from no network" separately from "network error due to server misbehavior", thus the proximate cause idea. This divide, between categorization and proximate cause, is part of why I think the DOM's exception categories evoke semi-useless exception hierarchies, even if they are aiming at a useful goal.

I don't fully understand everything you are saying above, but what I do understand I agree with :)

I think a proposed solution would help clarify.

For your Stream example above I would propose using an InvalidStateError but give it a couple of properties so that code could understand what went wrong. Maybe a property which contains "BaseWritableStream.write" to indicate that that was the function that failed, and a second property which contains "closing" or "closed" to indicate which wrong state was hit. This is definitely a very unpolished proposal, but might illustrate something that could work.

As you can see, I Have Opinions on this. I also know a number of community members who do as well. I'd love to discuss this further and perhaps pick up on that strawman idea you are suggesting, Allen.

That'd rock!

/ Jonas

# Oliver Hunt (11 years ago)

On Feb 5, 2014, at 12:05 AM, Jonas Sicking <jonas at sicking.cc> wrote:

On Tue, Feb 4, 2014 at 9:49 AM, Domenic Denicola <domenic at domenicdenicola.com> wrote:

From: es-discuss <es-discuss-bounces at mozilla.org> on behalf of Allen Wirfs-Brock <allen at wirfs-brock.com>

I've designed exception handling systems before and designed and worked with complex exception hierarchies. My main caveat is that they are somewhat of an attractive nuisance. It's easy to get into designing beautiful classification hierarchies of dozens of different kinds of exceptions, each with their own specific failure information that is captured. In practice, I've found very little actual utility for them.

My (more limited) experience is the same.

I agree. Creating hierarchies of error "types" seems fragile to me.

The other problem (an extension of “fragile”) is error objects when more than one global object/realm is involved. Once you’re in that scenario type selection (e.g. making use of |instanceof|) can no longer be used in any meaningful way.

—Oliver

# Domenic Denicola (11 years ago)

From: Jonas Sicking [mailto:jonas at sicking.cc]

I agree. Creating hierarchies of error "types" seems fragile to me.

A better approach seems like having a short list of flat error types. In cases where it might be useful to have a more specific error type, instead include additional information on the error. So for example, rather than having a NetworkResourceNotFound error, use NetworkError but add a property which indicate what type of network failure. Not sure if that was what you were proposing with the "proximate cause"?

Yeah, basically. Although I question the utility of the types at all. As discussed in the earlier thread about an InvalidOperationError, in practice bucketing errors into the short list of types isn't particularly helpful, I think.

(... a bunch of stuff I agree with regarding DOM errors.)

I agree.

I'm not sure that there's a difference between these two. Or at least I don't think there's a difference between the stuff that the DOM puts in .name, and that ES puts in the classname. They express the same thing just in different ways.

Yeah. And the divergence is unfortunate, although not that big a deal, since it's kind of minor and in practice nobody really tries to use either the classname or name to distinguish, from what I see.

I don't think we should specify the exact string that goes into these messages. It's good if implementations are free to add additional details if they see developers struggling with something.

But we could definitely do with more recommendations for implementations about what information to include here.

That would be cool in my opinion. The removal at 2 saddened me a bit. I wonder what other spec writers' perspectives are, given that nobody does so. Are we all just following accepted practice, but would be happy to switch? Or are there people that believe such guidance would be a bad thing?

I think a proposed solution would help clarify.

For your Stream example above I would propose using an InvalidStateError but give it a couple of properties so that code could understand what went wrong. Maybe a property which contains "BaseWritableStream.write" to indicate that that was the function that failed, and a second property which contains "closing" or "closed" to indicate which wrong state was hit. This is definitely a very unpolished proposal, but might illustrate something that could work.

Yeah, basically. My initial thought was some kind of identifier. E.g.

var closingError = new TypeError("nice human message");
closingError.cause = "wrote_while_closing";

var closedError = new TypeError("another nice human message");
closedError.cause = "wrote_while_closed";

(The underscores could actually be replaced with spaces, but then it feels less identifier-ey. Kind of a strange thing when you think about it.)

The idea would be that you try to maintain a globally-unique list of causes, so that people can match on exactly the cause they care about. Unlike DOMException names, you don't maintain a global list so that people can consult it and find a cause that matches; you maintain that global list so that people can consult it and not accidentally re-use a cause. It has all the normal fragility properties of a global registry, of course. Hmm.

Your idea of indicating which method caused the failure is a very interesting one as well. I wonder if it is related to the error stack trace in a universal fashion. Certainly for user-space errors it is not, since if you refactor to call a helper function outside of your public API that throws an error, you'd want the "causer" to be the public API method, not the helper function. But it might be for web platform APIs, since in practice they hide their helper functions using self-hosted or C++ tricks.

I've asked some Node people what info they think is most useful, since apparently they're starting to realize that they've built up some de-facto standards in Node core around this stuff and are thinking of making those more explicit and universal. It'll be interesting to see what they come up with. (Trevor Norris, if you're listening, feel free to chime in!)