72 comments

[ 4.7 ms ] story [ 143 ms ] thread
I'm pretty used to seeing strings being thrown. Maybe I am just old?
Same, although I always thought it was a terrible practice as I'm always reading error.message or error.stack straight away. And if an error does not have a stack then I will call the police on whoever wrote this shit!
I recently started throwing bare string instead of Error objects, just because of all the Error: prefixes that are prepended to the error message when you throw an Error. It just clutters up the stack trace.

More generally I wish there was an Error constructor that automatically records and the function, arguments, and other semantically meaningful information when called. File and line numbers do not make for a pleasant experience when inspecting a stack trace.

That was the norm maybe 15-20 years ago. These days it's a mortal sin to throw from something that isn't derived from Error.
I think the last time I threw anything else than an Error was in 2012 or so.

Old it is I guess - both of us.

I either throw an Error or something that extends Error. Makes sense, right?
Please, please, please do not throw anything that is not derived from Error. Throwing literals or other things makes error handling so much less predictable, stable and useful. throw/catch is already bad, let's not make it worse than it needs to be.
Nonsense. You can treat Throw like a fancy "goto the nearest catch" if you want to. It works fine.
If you're using throw for control flow, something is deeply wrong with your code.
`throw` is a control flow instruction. Simply using it at all controls flow within your code. Are you suggesting never to use it?
I think that the parent comment means "for control flow unrelated to error conditions".
I am suggesting to use it to throw exceptions. This narrow purpose is subtly hinted at by its name.
I don’t see how it’s hinted in the name. “Throwing an exception” is a phrase from within computing that exists because the feature exists in some languages, not because it came into programming from English.

There’s also nothing about “exception” that means “error”. If anything, modelling exceptions as errors is the anti-pattern. If you modelled all exceptions as descriptors, you could save Errors for when something actually goes wrong.

Honestly though, I’m employing Socratic argument. I am only defending this position to learn about the opposite.

Now I'm wondering about throwing functions as continuations. This sounds incredibly messy. I love it.
Why not generators then?
Because that wouldn't be perversely abusing a language feature for completely the wrong purpose.
I... May have done something like this once in a scripting language interpreter to implement return and break.

Not in JavaScript. And using continuations passed down from above would have been the much more correct way to do things.

There isn't though. Exceptions are flow control. There's nothing particularly special about them internally in a JS engine. https://ripsawridge.github.io/articles/exceptions/
I know. But what happens when someone inherits code that has a lot of this in it? They read a line, get to a throw, and then... how do they find all the catches that might apply?

Using that pattern is like a guarantee that your code will be spaghetti and meatballs.

This concept is so fundamental that it's even the topic of possibly the most famous CS article ever written[1], and I'd even argue that throw/catch is the worst-possible version of goto.

1. https://en.m.wikipedia.org/wiki/Considered_harmful

When Dijkstra wrote his article [1], in 1968, I have no doubt he was correct. His point was essentially that code should reflect the flow of the application as closely as possible because it's immensely hard to keep everything in your head if it doesn't in the context of programming in 1968.

The thing is though, tools have moved on in the past 54 years. Seeing the flow of your code is a lot easier now. Debuggers are amazing things.

While I think his broad point (that code should be as simple and easy to follow as possible) still stands I don't really agree with it here. "goto considered harmful" could be used to argue that anything that changes and obfuscates the flow of a program when you compare the source to the compiled output is bad regardless of what it is, and that's plain silly. We'd have to throw away all manner of useful things like exceptions, macros, inheritance, etc.

I like Dijkstra's writing a lot, and he did groundbreaking work, but we have to remember to consider everything in the context of when it was written. This is a good example of that.

[1] http://www.u.arizona.edu/~rubinson/copyright_violations/Go_T...

> in the context of programming in 1968

I was only pointing out that this is essentially the "original sin" of programming.

It's hardly an issue we've moved beyond, though. It's still one of the most widely-discussed anti-patterns[1] that I've seen in my career. There are dozens of essays and articles saying the same thing.

> Seeing the flow of your code is a lot easier now. Debuggers are amazing things.

There are no tools in any IDE or debugger that make it easier to look at a `throw` line and know all the places it might be caught. It guarantees the need for runtime debugging.

It's actually theoretically impossible to see all `catch` candidates for any particular `throw` in JavaScript because the dynamic nature of the language makes it possible for almost any function to call any other function at runtime.

This is still true in JavaScript of, say, a function call, but a function call can be more certain that its caller will be prepared to handle its output (especially when using TypeScript). When a `catch` is written without knowing all the possible things that can be thrown at it, it's very likely something will be thrown that isn't handled in the catch.

1. https://softwareengineering.stackexchange.com/questions/1892...

That sounds fine as a quick hack in your own code that you never intend others to use, but if something like that escapes your library into calling code (isn't always properly trapped by your exception handling due to a bug) you are asking others to deal with a significant change to a commonly accepted standard behaviour.
Isn't "ensuring you always throw Errors" the same level of code cleanliness as "keep a top level catch in your code" or "document the errors and exceptions of your API"? If you make your team do one, you can make them do another.
> you are asking others to deal with a significant change to a commonly accepted standard behaviour.

For a JavaScript developer, that's just Tuesday.

And that Tuesday lasts all day until Friday. Sounds about right.
Yes. In every way that goto is considered harmful it works fine.
If you need to use a rejection as a meaningful thing other than an actual exception then it really isn’t an exception is it? Instead fulfill the promise with an object that actually represents the state. Perhaps with a status property. Otherwise your code is going to look like a hot mess and you’ll just confuse generations of developers. Just because you can does not mean you should.
Can you provide some examples of specific situations in which throwing objects not derived from Error causes difficulty?

I ask for two reasons. First, because I find argument from authority to be a very annoying logical fallacy. I have actually heard people argue that in Python one should never use an exception to exit a loop, because "that's not how exceptions are supposed to be used". The people making this argument don't realize that every for loop in Python is always exited via an exception.

My second reason for asking is that I think your examples might all fall into certain categories. For instance, maybe there is a good argument for "never throw things not derived from Error in a way that they will escape your library, but within one library/module it's a perfectly fine technique". Looking at specific pros and cons would help uncover the cases where it is most and least harmful.

> The people making this argument don't realize that every for loop in Python is always exited via an exception.

It's just all memory addresses under the hood, so why bother with variable names?

Only throwing exception objects is a layer of abstraction that makes error handling easier to handle.

Nearly all python libraries follow this pattern, and it's extremely easy to pass any variable you want to your custom exception for you to handle however you want.

If you're throwing weirdly shaped things inside code other people don't need to interface with, that's fine. Go nuts. You'll be creating a circumstance where any new person working with your code has to learn that you're doing something strange, but you won't really be impacting anybody else.

The fact that JavaScript allows non-Error-derived values to be thrown means that the first thing that any code that works with a thrown value has to do to be technically correct is runtime type inspection. You can't even assume the thrown value has a stack trace attached to it. So while technically the ship has already sailed, we add further complication to everyone's error handlers when we emit non-Error values via throw... Everyone's catch code has to grow support for handling whatever Byzantine type we've decided to toss out of our code today.

I like to have a stack trace. Please. Just in case you’re using throw for control flow inside an abstraction, if that ever leaks, anyone downstream is going to be in pain trying to figure out what is happening.

You can easily attach whatever interesting values you want to an Error instance, go nuts:

    throw Object.assign(new Error(“suspended”), { suspend: somePromise, retriesRemaining: 4, favoriteColor: 1 })
There's a very simple argument for hygiene and convention: The halting problem should make code unreadable, it doesn't largely because of hygiene and the shared knowledge formed by programmer culture. These things restrict the language to something that's just Turing complete and much easier to read.

There is a such thing as legitimately unreadable code, I've had to deal with it. Usually it's abusing things like the blurry line between objects and hashmaps in languages like js and Ruby.

(1) I understand your point: consistently obeying sensible conventions makes code understandable and maintainable. That's a fairly good argument in favor of consistently using Error for all thrown things.

(2) One unrelated point: I suspect that the halting problem doesn't prove what you think it proves. The halting problem states that one can't build a tool to reliably analyze whether a given piece of code will end or will run forever. Some people seem to believe that this means some code will be "incomprehensible" -- that no code analysis can figure out what it does.

I am quite interested in the question of whether code can be made "incomprehensible" -- it has significance for things like securely protecting source code. But the halting problem would still be true even if "incomprehensible" code obfuscation is impossible and all programs can be "understood". Here's a good example of a program that might or might not halt:

  stop_at = input_number()
   x = 2
   while x < stop_at:
       if is_prime(x) and is_prime(x + 2):
           break
       x += 1
If the twin prime conjecture is true than that program always halts. If the twin prime conjecture is false than that program is an infinite loop whenever its input is larger than the largest twin prime.
I feel like async/await is a huge antipattern in a language like JS. They should have gone for lightweight threads instead.
I actually quite like it. Matter of opinion.
I love this thread, pseudonymous circle of unjustified likes or dislikes of a specific technology.
I'm not sure I like this thread.
I love the metacommentary on this thread, in that it matches the pattern of original commentary.
I don't like the metacommentary on this thread so much. The fact that the first two commends are dislike-like and the rest are like-dislike really ruins it for me.
I like how the replies are starting to make the little arrow thing. Reminds me of something...
(comment deleted)
Glorified Goto spaghetti
It is in some languages, but not in JS. It's definitely not good, but it's not goto spaghetti.
Goto is super helpful in C for error handling. But yes, let's continue parroting the same old rhetoric.
(comment deleted)
is await better than goto? frankly i prefer the latter
I agree and claim the burden is on those who say "it's fine, actually" to rebut the definitive article[async:0] on the subject:

[await:0] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-... (read it whenever you would like, it's good I Promise).

If you've only ever used Javascript, it's tempting to think this is some fact of nature, rather than bad language design.

While it is right, that the red/blue distinction is annoying at times it is the consequence out of the event-loop based approach created in 1995. Any other solution would have broken that more. It is assumed that the code we have is single threaded. That not being the case anymore leads to tons of concurrency issues. Right now I know that all my code, till the next await, will run (mostly) uninterrupted by external influence. No locking needed, nothing will change behind my back etc.

In a greenfield environment things certainly would look different, but unfortunately we are tied to past decisions till browser vendors decide to create an alternate universe (well, webassembly ...) and that has enough spread to rely on. But for that one would need agreement what that better future would be.

This isn't true. If you had stackful coroutines or something equivalent, you would be able to do all the things this thing does and you wouldn't have to pepper your callers and callers' callers with "await" whenever you add an operation that may take some time to a function for the first time.

We had this in Python and still have it in Lua, C, Zig, and so on, but the world has mostly gone the other way.

I happen to be working on an application which is largely written in Lua and uses the same event loop (libuv) as Node.

I can confirm that with careful engineering, application code doesn't have to suffer from the color problem. Fundamentally the trick is to yield after creating a callback, and resume into that callback when the thread has more data to process. A `foo:bar():baz():bux()` chain can pause execution in any of those methods and the user doesn't have to know which (some knowledge of whether this will happen /at all/ is required, only because you need a coroutine since Lua can't yield the main thread).

This isn't built-in, need to start with something like BEAM for that, but it's a nice sweet spot.

I think the issue they were getting at is code like this:

    function doSomething(doSomethingElse) {
      internalState.setUp();
      doSomethingElse();
      internalState.tearDown();
    }
Right now, you can safely assume this whole thing will execute to completion without anything else happening in the interim, so a lot of existing JS makes that assumption. If `doSomethingElse` can yield while other code executes the assumption becomes invalid.
It's worth reading that excellent article alongside Glyph's excellent article from the opposite perspective: https://glyph.twistedmatrix.com/2014/02/unyielding.html

A few languages avoid the pitfalls of threads, like Rust where its ownership system protects you.

This is a nice post. Off the top of my head, I can't think of any ways of avoiding this issue. Even Rust doesn't really avoid it because different requests could plausibly create their own representations of the payee and payer.
I think Rust avoids this issue because it limits you to a single mutable reference. So even if each thread constructed its own payee and payer, their underlying balances would still reference the same shared state so only one thread at a time would be allowed a mutable reference to payee and payer balances.
It doesn't limit you to a single in-memory representation of each entry in a remote database.
(The above comment was changed. When I replied it was talking about different threads reading from a database.)

In that case you’d be relying on the database rather than the programming language to manage shared state. (The article is more about how to safely manage shared state within a process.)

You could probably do it for a key value store, but I think it would take a custom database to do it for a relational database.
> It doesn't limit you to a single in-memory representation of each entry in a remote database.

If you have a remote database then you likely have multiple processes at play. For safety in those cases you need to rely on transactional safety at the database level or if transactions are not available then you may want to use a tool like TLA+ to check your distributed algorithm.

The example clearly has that though, otherwise it would not need

  yield from server.update_balances([payer, payee])
I personally prefer async/await over callbacks or promises. I find the flow better when I can make an Ajax call just await the result, rather than having to implement a success function.
The "lightweight thread" alternative would probably make the Ajax call blocking, so you could just launch a thread that waits for it to finish, then continues on with the rest of its instructions. The "callback hell" wouldn't exist in the first place.

This is exactly what Golang did, and in my honest opinion, it's a breeze of fresh air in the modern mess of processes/threads/asyncawaitcoroutines and all their combinations.

React 18 is in fact throwing Promises for the new implementation of Suspense working on Server Side Rendering. I found it brilliantly elegant. Throwing a promise in that context means "I'm not done yet and you should display a fallback"
This sounds interesting, but very error prone, no?

- Some code could throw a legitimate error, that would take the place of the promise.

- Some code may catch the promise and treat it as if it was an error.

This is in context of React components which are functions but only ever executed by the React runtime itself.

So there’s no other code that could catch the promise. And if another component throws an error first, that’s what needs to be handled anyway.

Javascript suffers terribly from the lack of asymmetric stackful coroutines.

All of this bizarre conflation of responsibility and extraneous keywords in the language, when all that was ever needed was `create`, `suspend`, and `resume`.

Fortunately, this is trivial to correct. Just grab a complete set of Go sources and documentation, hop in your time machine, and set the dial for 1995...