I looked a quick tutorial - it seems to be just a normal continuation-based approach. If want to have a single try/catch block for a bunch of code that calls many different async tasks among other things, how does that work?
It all flows through flatMap and other methods on CompletableFuture and you can catch the failures only once at the top level future that collects it all. That is the value of promises over callbacks generally. This might be a little convoluted to see what I am talking about:
The basic idea is that if you have a bunch of Futures you can flatMap them all together and have only a single exception handling block if that is what you want. Or you can handle the exceptions at any level and convert them. Makes it very nice to handle partial failures and the like.
The distinction between this and the coroutine-esqe approach provided by something like Quasar (linked in a sibling comment) is that even with the best of future/promise APIs, the callbacks/futures/promises paradigm of code is still... viral. Inverting the flow of one region of a project to bounce callbacks/futures/promises solves problems there, but when it's exposed at the end as a future/promise, now your calling code has two choices:
- Block a thread again on that promise
- Keep chaining out more promises
The former choice works, but limits scalability just like the poorly parallelized code we're trying to escape from. The latter choice is infectious: it keeps pushing the inversion of flow and scheduling concerns farther and farther out.
Using Quasar's fibers to call code that's "blocking" (but secretly just jumps back into use via the scheduler) scales the same way async callbacks do -- threads are always doing real work instead of blocking -- but it doesn't have the "viral" problem. When you take a stacktrace, it's exactly the same as your old blocking code: it's an actual record of how you got here! Callback based code invariably loses that feature of stacktraces, no matter how fine of an implementation of future/promise patterns is wrapped around it.
Incidentally, lest this read as a pitch for Quasar in particular, I should mention that this entire comment could all be regex'd into a pitch for Golang's goroutines and scheduler. Quasar is just the only good implementation of it for the JVM I'm currently aware of. (Other libraries have implemented some of the backing components, like Apache JavaFlow as long ago as 2006, but I experimented with it once and found it to be a far cry from the high-level APIs I actually want to use.)
The sample looks like any other closure-based framework, to me. Compare to C#, for instance, I can just do this:
try {
var a = await Function1Async()
var b = await Function2Async(a);
try {
var c = nonAsync(a,b);
await AnotherThingAsync(c);
} catch { ... }
} catch {
...
}
finally {
...
}
This generates the callback mess (every await is an async call), figures out the right parts of the exception/finally handlers to run, etc. etc. I don't see how this can be remotely approached if the only syntax you have is lambdas. You need some sort of rewriting system, either by a special keyword (C#), or monad-like thing (F# workflows) or something.