The Error type

# Peter van der Zee (13 years ago)

I was jesting a bit in the other thread (esdiscuss/2012-August/024602) but the more I think about it, the more it makes sense. JS should have an Error primitive type. It would make the "failed" return type for most actions more consistent. The word consistent is not without a bit of irony, due to the nature of the Error type, which you can read below.

Tl;dr the Error type would be instanceof the Error object (like strings are to String). It would have an internal property containing an error message, which can be empty. Comparison and coercion would be very special, in that it should be able to mimic all the fail values currently used in JS. This includes -1 when coercing to a number, null when coerced to an object, and false when coerced to a boolean. It always compares equal to itself (like the different NaNs do) and could only be distinguished from one another by checking the result of error.toString(). An Error type value would be created through Error.primitive('foo'). There would also be Error.isError() that works like isNaN().

Inconsistent

JS has various ways of letting the user know that an operation has failed. Some examples include str.indexOf(), regex.exec(str), and delete(window.eval). There is no single way of handling these errors and there's no way to get a more specific reason from these failures because the returned values are primitives. So unless a method throws explicitly, you're just stuck with a "computer says no".

New type

So let's introduce a new type; the Error type. A value that's indistinguishable from exisitng error denoting values, but that still holds a special semantic value. It would also be able to hold a message interally, one you could only get by calling .toString() on it.

Backwards compat

Of course, introducing a new type this late in the game is a problem. The JS language does not have the luxery of simply introducing language breaking features when moving to a new major version. I don't think that needs more explaining, we all know this. However, I think this Error type could be introduced while keeping virtually all language semantics as they are. This does mean the type will have some very ugly semantics. But those should not really bother the user that does not want to use them.

We don't want to change the API for these existing mechanics because that would be too breaking. So instead we could introduce a type that wouldn't. It just so happens to be that for the various types of errors JS might know about, it always returns at least the same primitive value for any type. Meaning NaN or -1 for number, false for boolean, undefined and null for ... well, undefined and null. (The only one I'm not sure about is zeroes. I think all the API's that might return a number, return -1 for failure or NaN for computational issues, but maybe I'm missing one that returns zero...?) So let's make this Error type match and coerce to all these types...

In other words, when comparing (weak, strict, or relative), always convert the Error type to the other type explicitly according to this table:

Undefined -> undefined

Null -> null

Boolean -> false

NaN -> NaN

non-NaN Number -> -1

String -> the error message? there's no fail return value that returns a string

Object -> null

Error -> error

I'm not sure about String, but since there's currently no API that returns a string in case of errors, this could just return the internally stored error message. Could allow one to compare error messages easily... Individual errors should be indistinguishable from one another in comparisons. They'd behave like NaN in that regard (in the sense that there are different NaN values but in JS we can't distinguish them).

Error

I don't want to bog down the syntax with a literal for this Error type and I don't think that's even necessary. The fact that "error" might be a pretty common keyword only adds to this. But I think it'd be quite elegant to create Error type values through Error.primitive(msg). (Ok, I started this with fail as the name of the type, so primitive is not as elegant, but feel free to bikeshed that into something better ;)) In fact, I think it would make very much sense to make error an instance of Error. We can add special cases for calling Error(primitiveError) to behave like String("foo") would. Error.prototype.toString would also become a special case for Error type. Or rather, it would probably be extended to first check for an internal [[ErrorMessage]] property before checking an own message property.

(We could make Error('foo') return a primitive instead of a new Error object, but I think there's too much legacy usage of calling Error to make that change now)

So the built-in Error object would get two new properties; .primitive(msg:string) and .isError(val:any). Luckily the Error object is not as popular to extend as String or Number are, so I think the chances on collisions for these methods are small (though I don't have any actual data on this).

Typeof

I'm not sure about the result of the typeof operator. I think typeof could/should simply return "boolean", to keep it backwards compat as much as possible. Yes, that would kill it for the cases where you're checking for "null" (regex related methods) or "number" (but who does that...?), but I'm not sure I've ever seen code that uses typeof to check for an error. Which makes sense because in most cases the returned type is the same as when there was no error. Only for regex cases the type is different (in that Null is it's own type), but people simply do an if check in that case. So I'd say boolean to prevent old code that checks for primitives and would skip this.

Built-in messages

An error type could help to give some better insight to why a certain error was thrown. Although in most cases the error can only be one (like -1 being "not found" for array.indexOf) there are some cases where it might (like "why am I getting NaN here?"). So the specification could specify default error messages for certain steps in the algorithm. We could choose to specify the actual error, or go the route of AS3 and only specify error codes. An implementor could then decide on its own what the textual message should be. Especially in multi-language environments, I think that would make more sense. Also with to dynamic content ("#1003: tried to multiply 'foo' by 5 in foo.js:53"), error codes would be the way to go. Code would only have to take a fixed substring of the error message in order to determine which built-in error ocurred.

Pros

  • It would make give the language a tool for better semantics in cases of an error
  • It's fully backwards compatible, no exception
  • It's very flexible
  • Allows you to add a message to your error (without throwing, while keeping backwards compat for primitives)
  • A primitive Error type makes a lot of sense and would be consistent with some of the other built-ins
  • Easier debugging for NaN cases

Cons

  • It's a weird type
  • Adding a new type to the language
  • Weird constructions possible
  • Inconsistent type
  • Potentially very confusing
  • What to do with typeof
  • We already have throw for explicit errors

error vs fail

I started writing this proposal with "fail" as the name of the type, but later figured that "error" makes much more sense. Especially with there already being an Error built-in object. And of course there's already the precedence of all other primitive types that have a parent class.

Conclusion

I think the Error type would be nice to have. I wouldn't mind it if the spec was a bit more consistent in the area of errors and I certainly would like to see more clarity on certain errors. Why did that delete fail? Why am I getting a NaN? Currently there's no easy way of getting to the bottom of that. I think it would be a good addition to the language. I feel it does not change the language in a way that drives it away from being "like JS".

I don't think this has a high chance of making it into the language though. This would be a pretty radical thing to appear in the language, even if it's just due to it's very dynamic nature. Consider other factors that come into play for this decision and I don't think the odds are in my favor. But I still think it's worth putting this idea on the table.

So I hope you liked it :)

# David Bruant (13 years ago)

Interesting topic.

I'd like to share some experience and thoughts which are complementary and may influence the proposal. I've been playing with promises with some time and the way errors are taken care offers different opportunities than "normal" error handling.

When using promises (I used the Q library), most errors are now caught by the library and you rarely find them in your JS console which leads to some frustration especially when playing with APIs a-la-mocha with "done" functions you have to call when you're done (because in case of a thrown error in a test case, you don't call done and the error is absorbed by Q if you don't explicitly log it). The error forwarding mechanism is however much less violent and intrusive than throwing (which unstacks function calls) until being caught because the failed promise is a regular value. It is the responsibility of those who have access to the promise to decide which "channel" they want to listen (the "then" or "fail" "channel"). If you don't want to listen to one channel (like the fail channel most often), you can keep doing what you want without worrying about it. The next piece of code who cares about the fail channel will read what's in it and choose to forward or alter the value or "empty" the "fail channel".

Based on this experience I'd like to suggest a slightly different approach to the "error type". Since it's quite a different model (from what exists already and Peter's proposal) and may require major rewrite of existing engines (because it deals with JS value representation), I'm putting it as a thought exercise and food for thought for (compile-to-JS?) language designers. The way I see it, conceptually each value would have 2 slots, one for the "normal flow" case and one for the "error flow" case. Syntax constructs would decide which slot is being used. In most cases, it would be the "normal flow slot" (like with promises where most of the time, one write the .then without the .fail). Because of the 2 slots, there would be no need for a new error type, it would be a part of all types somehow.

If considered to be applied to JS, everything could be considered to currently work with just normal flow values. However, for error cases (like indexOf returning -1), the error slot could be filled in and some new syntax/functions could help make more clear that we're treating the error case (and not caring about the exact -1 value or empty string or whatever the "normal flow" value has been decided to be). The good side of 2-slots values is that errors can be associated with any value of any type. Values thrown would have both their normal and error flow values to the same value. It's likely that engines could optimize by optimistically consider being in the "normal flow" most of the time and there would be a negligible performance overhead if error slots aren't abused I think (but it'd be a major rewrite anyway I think).

Could what I just described implemented with "value proxies"? I'm not very familiar with the proposal.

Anyway, as I said, food for thought, not an actual idea to be added to JS.

David

Le 16/08/2012 20:56, Peter van der Zee a écrit :

# Brendan Eich (13 years ago)

David Bruant wrote:

Could what I just described implemented with "value proxies"? I'm not very familiar with the proposal.

See disnetdev.com/talks/virtual_values.pdf and users.soe.ucsc.edu/~cormac/papers/oopsla11.pdf -- and probably more from Cormac and Tim that I don't have at hand right now. Lots of applications, no hardcoding for just "error handling".

An idea I first heard from Dave Herman: make an undefined proxy that tracks its origin (source coordinates, stack backtrace) and return it instead of the one true undefined value, so you can find where that missing property was dereferenced.

# David Bruant (13 years ago)

Le 17/08/2012 16:11, Brendan Eich a écrit :

David Bruant wrote:

Could what I just described implemented with "value proxies"? I'm not very familiar with the proposal.

See disnetdev.com/talks/virtual_values.pdf and users.soe.ucsc.edu/~cormac/papers/oopsla11.pdf -- and probably more from Cormac and Tim that I don't have at hand right now. Lots of applications, no hardcoding for just "error handling".

Thanks for the pointers.

For that matter, I tend to prefer the API described in the paper, especially with regard to unary/binary trap as opposed to the current strawman which lists every single operation. I'd like to add a point about the following use case:

if(!x){}

If x is a value proxy, its unary trap is called. If the result of that is a value proxy too, its test trap will be called; otherwise, no test trap is called. I think it's unfortunate. If we had an if! statement, the above could be rewritten as

if!(x)

and the test trap of x would be called. The opposite of its return value would be used for the test (because of the "!"). Maybe it's a minor thing, I don't know.

An idea I first heard from Dave Herman: make an undefined proxy that tracks its origin (source coordinates, stack backtrace) and return it instead of the one true undefined value, so you can find where that missing property was dereferenced.

It seems that the "tainting" extension described in the paper would be very close to this idea. The careful reader will note that this could be implemented by engines today ;-) Actually, to track down missing dereferenced properties from objects you don't own (built-in objects especially), I think it's even necessary. I've filed bugzilla.mozilla.org/show_bug.cgi?id=783910 for SpiderMonkey to keep it in mind.