See this is what the OP is getting at, this is only true for async implementations that don't have any parallelism. That doesn't have to be the case, there's no reason that your javascript runtime couldn't take
await foo()
await bar()
and execute them in two threads transparently for you. It just happens, like the Python GIL, that it doesn't. Your JS implementation actually already has mutexes because web workers with shared memory bring true parallelization along with the challenges that come with.
In the case of javascript, it's only allowed to do that when you can't detect it, situations where foo doesn't affect the output of bar. So as far as pitfalls are concerned it does one thing at a time. The rest is a hidden implementation detail of the optimizer.