I've criticized Node.js in the past on this site but I actually only recently realized what is really
wrong with it in a strongly-grounded computer science way rather than an intuitive way. Or rather, wrong with the asynchronous event-based programming style in general that Node.js adopted, rather than Node.js in particular (which is a fine implementation of the bad idea).
Does anyone remember the structured programming arguments that occurred, oh, ten years before I was born? There was a lot of arguing back and forth, but one of the arguments made was that when your program was structured, your position in the program means something. Just making up some quick psuedocode:
def x(y: int, z: int):
for i in 1 to 10:
--> print i * y + z
def a(b: int):
for i in 1 to 5:
print i
x(b, 3)
At the line that I marked, the very fact the program counter is pointing there, and by extension the stack trace up to the point given, means certain things. We know we have an x and a y which aren't just "undef", we know we're in a call from a (in this case), and therefore, any preconditions or invariants that are provided by those functions are also in effect at this point in time. One of several reasons spaghetti code is bad is that the program counter means much less; we don't know how we got there, we don't know what invariants are in effect, in the old school assembler case we know almost nothing at all, for all we know we just lept here with a goto.
As is almost always the case, this is a small trivial example, but when you start layering many things on top of each other, layering in functions that provide safe file handling or other resource management or state machines or any of the other tools we've built on structured programming or its smarter child Object Orientation, it becomes difficult to in some cases impossible to follow all this in your head. Or deal with the work to make this stuff compose together properly without layers actively stomping on each other. After spending some time with the modern functional world and their increasing focus on composability, working with async event programming feels like stepping back in time 20 years.
Asynchronous event-based code is not as bad as old-school assembler, because the event handlers themselves are still using the ideas of structured programming internally. But the code as a whole is spaghetti code. I'm not the first to say that, but it turns out upon reflection it's not a slur or a metaphor, it is actually descriptive and fair. Async event code actually does share many of the critical properties of spaghetti code, as the term was first used. You don't have a call stack, you don't have the invariants, you're just adrift in the code.
Of course with massive amounts of discipline you can function anyhow. You can program structured code in assembler with massive amounts of discipline. But A: you are spending valuable developer mindpower maintaining that discipline which is better done by a language/runtime/VM, no matter how smart you are you're still better off spending your smart on something other than raw plumbing and B: it's actually harder than you think. We've so thoroughly, utterly internalized structured programming since even before I was born that we can't hardly even see what we're getting out of it, and consequently we don't easily realize what we're giving up when we adopt this style. We aren't any better people than our assembler ancestors, we're not particularly more disciplined than them, and they switched to structured programming for a reason.
I'm this critical because I'm actually trapped in this style at work, fortunately just in one of several subprograms but it's still annoying as hell and the one that always takes far longer to work with than I'd like. It's an excess of experience, not a deficit, causing me to be this critical. And I've really come to loath this style. Compiling is for computers, not humans.
That's what Erlang and similar languages that can take care of the asynchronousness at the VM/language level bring to you; they bring you at least back up to structured programming in power and safety, and possibly beyond. And the arguments about how wonderful async event based programming is and how you've got it all under control and how it's performant and not a problem sounds to me like an absolute repeat of assembler programmers ranting against structured programming back in the day, to an almost scary degree... and every bit as correct and likely to win the future.