If you're already at the point where you are utilizing compilation, why not implement a compiler-level async/await solution (probably based off of a loop + a state machine, a la regenerator).
Even with generators we still need to yield something or await on something. C# awaits on Tasks, in JS Promises would be a logical choice. You definitely need a value though - you can't await for node style callback functions as they provide no value to yield (or otherwise pass to the runtime) between calling the function with a callback and your callback getting called.
Even so, lambdas, observables and some FP constructs such as map/reduce/filter/etc can potentially eliminate the need for imperative style code. There are also some semi-imperative solutions to certain problems that work okay [1]
Another advantage of functional code is that its easy to extend - adding a combinator that implements filtered (typed) catch for promises is a lot easier than implementing a language construct such as `catch (e if e.code == 404)` for generators.
var only = (predicate, handler) => e => {
if (predicate(e)) return handler(e) else throw e;
}
promise.catch(only(e => e.code == 404, e => {
handle...
}))
Here is another example (its a bit of an overkill though :)
If you're already at the point where you are utilizing compilation, why not implement a compiler-level async/await solution (probably based off of a loop + a state machine, a la regenerator).