
Asynchronous Error Handling - llambda
http://swannodette.github.io/2013/08/31/asynchronous-error-handling/
======
abecedarius
This criticizes promises on two grounds: they need to propagate and handle
errors, and the Javascript syntax looks different from the corresponding
sequential code. OTOH with channels, if I understand it, the system needs to
propagate and handle errors ("If an asynchronous process writes an error onto
its channel we will convert it into an exception") -- can you explain the
advantage? And the better syntax we're offered is ClojureScript or yield.

For a cleaner comparison we should avoid unnecessary differences in the code.
Let's fix that:

    
    
        getTweetsFor("swannodette")
          .then(function (tweets) {
            return expandUrl(parseUrls(tweets)[0]);
          })
          .then(httpGet)
          .then(
            function (responseBody) {
              console.log("Most recent link text:", responseBody);
            },
            function (error) {
              console.error("Error with the twitterverse:", error);
            }
          );
    

Besides yield, ES6 has shorter function expressions (you can try them right
now in Firefox):

    
    
        getTweetsFor("swannodette")
          .then(tweets => expandUrl(parseUrls(tweets)[0]))
          .then(httpGet)
          .then(responseBody => console.log("Most recent link text:", responseBody),
                error => console.error("Error with the twitterverse:", error));
    

I'd agree that this could be better still; I'd like either built-in syntax
dealing with promises, like E had 15 or so years ago, or something more
general like Haskell's do-notation. But yield seems nice too.

~~~
swannodette
Thanks for the cleanup though I think it's easy to see it's not much of an
improvement. It's also not really equivalent to the core.async / ES6 Generator
version as the error must propagate through the promise pipeline instead of
short circuiting as it would thrown as a real exception.

UPDATE: also see the comment below about stack traces. Since we don't have to
wrap promises at every step of the pipeline you can recover much cleaner stack
traces devoid of useless promise machinery.

Which leads us to the points from my other post about the problem with
promises :) While this is fine for coarse grained asynchrony like this example
- the allocation overheads for each step of the pipeline is unacceptable for
say mouse events - forcing you to leverage another abstraction like Rx. So
does JS need sugar for _both_ promises & async streams? I should hope not, and
core.async / ES6 Generators demonstrate that you don't need two different
abstractions and you can get a lot more efficiency and less error indirection
than promises can possibly offer. This is more or less what Erik Meijer's been
saying about C# async/await for a long time now.

~~~
abecedarius
_not much of an improvement_

I just wanted our thumbs off the scale.

 _the error must propagate through the promise pipeline instead of short
circuiting_

Along a queue vs. up a stack, I'm not sure there's a fundamental efficiency
difference, if that's what you're saying. Maybe. When it comes to distributed
apps, OTOH, just one round trip saved by automatic promise pipelining
[http://erights.org/elib/distrib/pipeline.html](http://erights.org/elib/distrib/pipeline.html)
can outweigh a _lot_ of that sort of thing.

 _stack traces_

That post says Q's stack traces are fine if you set a flag, and the same for
generator-based libraries (optional because it costs extra). I expect
ClojureScript does better because it can compile its own code, and the same
would go for a promise-based language (or JS preprocessor).

So this is what you were getting at with "needs to propagate and handle
errors" vs. ClojureScript -- you're saying the latter's more efficient?

 _does JS need sugar for both promises & async streams?_

I'm not sure what you mean about async streams, but I wasn't seriously after
more changes to JS syntax, just wishing (1) E had taken off and (2) new
languages learn from it.

~~~
swannodette
_That post says Q 's stack traces are fine if you set a flag, and that
generator-based libraries also pay extra for full stack traces. I expect
ClojureScript does better because it can compile its own code, and the same
would go for a promise-based language (or JS preprocessor)._

Q explicitly says that you don't want to enable stack traces for production,
which seems like a fairly high cost to pay. Neither core.async nor ES6
generators need to do anything special to have clean traces - it's fundamental
to how the system works.

 _I 'm not sure what you mean about async streams, but I wasn't seriously
after more changes to JS syntax, just wishing (1) E had taken off and (2) new
languages learn from it._

Sorry for not being clear, my posts are all part of continuum. My point is
hardly anyone uses promises for fine grained asynchrony because of the costs
and debugging indirection. My point is that you can do both coarse/fine
grained asynchrony at little cost and without obfuscating your errors during
development or in production if you move beyond promises.

~~~
abecedarius
"the debugging story for generators is somewhat bad, especially because of the
missing stack traces for thrown errors. Fortunately, there are solutions and
workarounds, like those implemented by genny (obtrusive, reduces performance)
and galaxy (unobtrusive, but requires native modules)."

From the post you brought up, [http://spion.github.io/posts/analysis-
generators-and-other-a...](http://spion.github.io/posts/analysis-generators-
and-other-async-patterns-node.html) \-- by the author of Genny. As I said,
that's at least qualitatively the same as Q; quantitatively I haven't looked.
At [https://github.com/spion/genny](https://github.com/spion/genny) there's
the example line

    
    
        require('genny').longStackSupport = true
    

and "This results with CPU overhead of approximately 500% and memory overhead
of approximately 40%. In the future, the overhead will probably be eliminated
in node but not in browsers." The benchmark post says Catcher is worse.

The README for Q shows the same switch and says "This feature does come with
somewhat-serious performance and memory overhead, however. If you're working
with lots of promises, or trying to scale a server to many users, you should
probably keep it off. But in development, go for it!"

I'm not a current user of any of these systems, but this is why I said the
first-order cause of any performance difference appears to be that
ClojureScript is not a library.

~~~
swannodette
I can't really speak to other generators libraries nor their how good their
debugging stories are based on how good or bad their designs are. core.async
is just a library and not directly part of ClojureScript. I do know that we
need nothing to support good traces and we do not take any performance hit of
any kind for this "feature".

~~~
abecedarius
Not a Javascript library. The post advocates generators for Javascript
programmers. For core.async to tell us the efficiency of JS code of this
style, it'd need to expose a generator-based interface and compile to
generator-based code like a human writes. If I understand right, it doesn't,
and the JS libraries that do work this way are much less efficient when full
stack traces are turned on. (If there are others, I hope someone will chime
in.)

If you think those two libraries are just bad, I'll update somewhat in that
direction.

~~~
swannodette_
No the post advocates generators plus a competent channel abstraction. It's
going to be hard to compare generator libraries to core.async, far as I can
tell none employ the comprehensive set of optimizations that we do for time
and memory. Perhaps when someone ports our ideas to JavaScript a real
comparison can be made.

------
rzimmerman
This would be the equivalent in Kal:
[http://goo.gl/TwsL2h](http://goo.gl/TwsL2h)

It's a different approach, but it doesn't require ES6 or any libraries (once
compiled). Forgive the slight funkiness with the indentation.

    
    
      try
        wait for tweets from getTweetsFor "swannodette"
        url = parseUrls(tweets)[0]
        wait for responseBody from httpGet url
        print "Most recent link text: " + responseBody
      catch error
        console.error "Error with the twitterverse: " + error

~~~
swannodette
This is nice but I am of course biased about the CSP model. While `wait` is
good for one off tasks, I now prefer CSP channels as they just as easily
capture Rx style interactive event programming.

------
spion
I wrote a detailed comparison of JS async coding patterns here (emphasis on
ES6 generators):

[http://spion.github.io/posts/analysis-generators-and-
other-a...](http://spion.github.io/posts/analysis-generators-and-other-async-
patterns-node.html)

Unfortunately most patterns that consolidate error handling also result with
useless stack traces, or have to resort to performance-reducing trickery (Q).
This also includes generators.

~~~
swannodette
This is another problem with promises is the the mangling of the stack trace.
In my version the stack traces are more or less left clean since you don't
have to wrap at every step of your async pipeline.

------
film42
This is a very interesting idea. Although I still prefer the "jquery" way of
passing in success/ failure as options. In fact, depending on what framework
I'm using, I even trigger a success or failure event so it looks more like..

model.fetch();

model.on('error', function() { throw new Error(); });

But this article really got me thinking though, thank you!

~~~
ryanto
The problem with passing success/failure as options is they don't compose
well. Try to write the twitter example in the blog post this way. Then try to
abstract each step as a reusable piece. And lastly have a mechanism for
handling errors at any step. This looks ugly and is hard to follow. Promises
(or generators) provide an API for thinking about async code as composable
parts, which leads to easier to understand functions.

------
markus2012
There is a problem with relying on setImmediate() in your channel
implementation:

1\. httpGet...

2\. setImmediate() in a tight loop using 100% CPU until httpGet() returns

...

I'd rather not burn ~100% CPU for what could be a couple of seconds waiting
for a slow web server (even if we're nicely burning it).

------
MaxGabriel
swannodette, would you consider upping the font size 1-3px? The font is very
thin and I found it hard to read at even 15px.

~~~
AndreasFrom
I agree. I find Computer Modern to be more suited for mathematics and making
it justified as well is not good for readability.

~~~
nimish
But TeX justifies (and hyphenates and microtypes...) by default? I'm curious
why it would hurt legibility. It's the default text font for non-math stuff
there too

