Ideally, all functions should be async-transparent. That is, it should be up to the caller to decide how to invoke it. This is usually done with some form of green threading.
It all works great, right up until the moment you have to interop with another language/runtime that doesn't understand your bespoke async. Callbacks (and layers over them such as tasks/promises) are uglier and necessitate opt-in async, but you can interop them to anything that speaks the C ABI.
Green threading is multithreading right? Isn't Javascript singlethreaded? I would assume adding multithreading would break tons of code that relies on it running singlethreaded.
It would break the same as sprinkling "await" all around your codebase. Asynchrony in general is not free - you have to redesign around it regardless, and deal with the issues it introduces.
Callbacks have been there, but most code wasn't written with that in mind. And you still need some synchronization in async code - even if it's all scheduled on a single thread - due to re-entrancy issues.
It all works great, right up until the moment you have to interop with another language/runtime that doesn't understand your bespoke async. Callbacks (and layers over them such as tasks/promises) are uglier and necessitate opt-in async, but you can interop them to anything that speaks the C ABI.