Consider the following code:
class Foo {
String bar;
synchronized String getBar() {
if (bar == null) { // OK, we all know this is bad in the Java world, just bear with me for this sample code
Future<String> realBar = …; // make a call that returns a CompletableFuture<String>
bar = realBar.get();
}
return bar;
}
}
You're proposing that it's somehow translated more or less to:
var _symbol$bar = Symbol("Foo.bar");
class Foo {
async getBar() { // Note: transformed to 'async', no 'synchronized'
if (this[_symbol$bar] == null) {
var realBar = …;
this[_symbol$bar] = await realBar.get(); // transformed to await
}
return this[_symbol$bar];
}
}
Now imagine that while await⋅ing realBar.get(), getBar() is called again, on the same Foo instance (triggered by an event; for example, getBar() is called from a click handler, and the … returning the CompletableFuture<String> fetches some resource through HTTP).
Looking at the original Java code, you'd expect that the second call is blocked until the first one terminates and releases the lock on the Foo object, so the first call would set the bar field and the second call would skip the 'if' branch.
In JS though, the second call would *enter* the 'if' branch and make a second call; then, when each 'realBar' is completed, the private (through Symbol) property is set: twice; and if the call that returned a CompletableFuture<String> is stateful, that means the state has been modified twice.
What kind of JS would you produce that'd prevent this from happening?
I wouldn't trade "emulating CompletableFuture#get" for "bloated JS and a much more complex compiler". The Pareto rule tells us that you should just embrace asynchrony and live with only the non-blocking API (CompletionStage basically, plus getNow() and a few others).