The Error type

Peter van der Zee ecma at qfox.nl
Thu Aug 16 11:56:25 PDT 2012


I was jesting a bit in the other thread
(https://mail.mozilla.org/pipermail/es-discuss/2012-August/024602.html)
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 regards 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 :)

- peter


More information about the es-discuss mailing list