All this async code without decent locking primitives is leading to a rabbit hole of race conditions...

It doesn't matter that it's all single threaded if all your function calls may or may not block and run a bunch of other code in the meantime, mutating all kinds of state.

I feel like JavaScript developers of the 2020's are going to relearn the same things the C programmers of the 1990's learn, just a few levels of abstraction higher.

JavaScript uses non-preemptive multitasking. It's trivial to write locking primitives. Indeed there are already many: https://www.npmjs.com/search?q=mutex

You could even have a method decorator (https://github.com/tc39/proposal-decorators) that emulates Java's "synchronized" keyword.