Asynchronous Error Handling
swannodette.github.io
swannodette.github.io
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.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.
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 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.
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.
From the post you brought up, http://spion.github.io/posts/analysis-generators-and-other-a... -- 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 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.
If you think those two libraries are just bad, I'll update somewhat in that direction.
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: " + errorhttp://spion.github.io/posts/analysis-generators-and-other-a...
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.
model.fetch();
model.on('error', function() { throw new Error(); });
But this article really got me thinking though, thank you!
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).