Error objects in W3C APIs

Allen Wirfs-Brock allen at wirfs-brock.com
Tue Feb 4 09:24:36 PST 2014


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.

Allen




More information about the es-discuss mailing list