I think Python also has async / await support today.
async / await isn't really concurrency. It's a mechanism for picking up the job after something else has been done (perhaps like a callback). In a way it's been an advantage of Javascript: one or two threads do all the work in a timely way without all that messy thread scheduling and context switches.
No, I do not consider any runtime that can't run on multiple CPUs simultaneously to have "good" concurrency support. It would, at best, be bad concurrency support. Better than "no" support, sure, but not good in 2021. If a new language came out with that support, nobody would call it "good" support, they'd call it failing to even raise the table stakes a new language needs nowadays.
Because if "good" concurrency support means single-process multi-threaded on multiple cores running with enough locks that you can have multiple threads executing code simultaneously in a shared memory space then a lot of languages are going to fall down or punt all responsibility for doing that safely to the programmer which might as well be no support.
I should say that "hack" here isn't necessarily a perjorative. There are reasons for communities to create and deploy those. There are plenty of cases where existing code can be leveraged to work better than it could without it, and that's the relevant standard for whether something is useful, not whether or not in a parallel universe the code could have been written in some completely different way or whether in some hypothetical sense if you could push a button and rewrite something for free you'd end up with something better. Hacks can be good, and every language will pick them up at some point as history evolves around it and some of the core assumptions a language/runtime made go out of date. But it's still a hack.
While OS process boundaries do provide some nice features, they are also in general overkill. See Erlang and Pony for some alternate riffs on the idea, and if you look at it hard enough and understand it deeply enough, even what Rust does can provide some similar benefits without raising a full OS process boundary between bits of code.
But I guess we'll just have to leave it there.
For example, CUDA exposes a parallel programming model with low support for actual concurrency. Similarly, OpenMP is mostly useful for parallel computations that rarely need coordination. C#'s Parallel package is another example.
By contrast, concurrency often rears its ugly head even in single-threaded workflows, even ones that are part of larger multi-threaded programs.
JavaScript and the related ecosystem in the browser lack a number of things, that are then patched over using additional complexity such as web assembly, bug handling/ feature workarounds in applications, mobile apps that you basically have to develop, because the web isn't useable/ performant enough for some stuff. Of course we have some extra experience now and it is easy to criticize in hindsight. E.g. the decision to make JavaScript less LISPy was perhaps a good decision for it's large adoption but longer term the homoiconicity could have been used for easier generation instead of e.g. stuff like WebAssembly. We also know, that stuff like Clojure(Script) is possible now. We also know, that we really want a native 64bit integer type instead of hodge-podge solutions with reduced precision of 53 bits or big decimal with worse/ complicated performance characteristics.
For the last 12 or so years, the web and related technologies became an application platform and a video delivery network with demands that rival native applications. We even use JavaScript on the server side through Node.JS. This has enabled tremendous things to happen but it is also perhaps the least stable platform to develop for. The web actively breaks some of the more complex applications that it has enabled precisely because the foundations are not solid enough. The current state of affairs is we keep piling on new and shiny APIs and technologies, that perhaps have considerable merit but we don't really fix the broken things in a way normal devs could keep up. I mean, how do you imagine to keep up, if even GMail, Google Maps, YouTube, Facebook and all its web apps, even major news sites and other applications backed by big companies still have rather obvious bugs in these products? I guess, "move fast and break things [for the later generations to fix all this mess]" it is.
For Python you either have to have your library pause execution of Python bytecode and do multithreaded things on your memory space, allocate an isolated memory arena for your multithreaded work, copy data in and out of it, and then spawn non-Python threads (see PEP 554 for an IRL example of this idea with the Python interpreter itself), or copy data to another process which can do the multithreaded things.
With PEP 554 (right now using the C API) I personally have no issue with multithreaded Python since the overhead of copying memory between interpreters is fast enough for my work but it is overhead.