In my experience in practice there is no big difference, really, its just a matter of a few keywords.
As for "having to change an entire call stack", that just stopped happening after a while. The rule of thumb was that if the function is impure (e.g. relying on mutable state) then it will likely become async, too. That lets me plan ahead.
Generators are just iterators, and are synchronous. The consumer asks for a value with next() and the value must be computable (even if that value is a Promise).
If you have an async function (one that returns a Promise or in the future a Stream), and you need to call it from a sync function, and somehow incorporate the future value into the return value of the sync function, you're stuck. You have to async-ify your functions all the way up the call chain.
With blocking this isn't a problem. We just need environments that make blocking cheap.