>> - Node.js's "callback hell" async paradigm is probably the worst of any popular language/framework today.
Now we have Promises, so this argument has become completely invalid.
Even before Promises, avoiding callback hell was relatively easy - You could just define your functions one above another (even if you used callbacks) - nesting anonymous functions was NEVER necessary in JS - People just did it because they were too lazy to actually declare the function before using them.
>> - Ignoring the fact that JavaScript lacks static typing, its inconsistencies make it a pain even compared to Python or Ruby. It's so bad that entire applications are written in CoffeeScript and transpiled just to avoid JavaScript.
A lot of the main concepts from CoffeeScript have since been introduced as part of ES6. A lot of JS developers never understood the purpose of CoffeeScript - It was merely a gateway to encourage stubborn Python developers to switch JavaScript (and was very successful). A lot of CoffeeScript developers who did not like JS now like it thanks to ES6.
>> - NPM is an absolute clusterfuck. It's a matter of "when," not "if" your application will break because a dependency violated semver. Oh, and good luck developing offline.
The main problem was that some people wrote modules which had very wide dependency version ranges and this caused issues when a dependency would be updated behind the scenes in a non-backwards-compatible way.
npm has always allowed you to specify strict version numbers for your dependencies - I have always followed this approach but a lot of developers didn't. Since this issue came to light, you'll find that most popular modules now use proper semantic versioning or strict version numbers when referring to dependencies.
>> - The over-engineered UI frameworks and tooling have bled over and poured gasoline on the clusterfuck.
I'm sure that some projects are 'over-engineered' but you'd be surprised how often topics like 'the Linux philosophy' comes up in discussions between Node.js developers. Many Node.js developers are really conscious about complexity.
>> - A community with the attention span of a goldfish
I agree with that, the community is all over the place. I think part of the reason is that no consensus has been reached regarding a lot of things. That said, it has settled a lot in recent years. I don't think this is such a bad thing; better be unsure than forcing everyone in the same (wrong) direction.
>> - Scaling is a pain in the ass.
Moreso than Go, but I wouldn't call it a pain in the ass - I think it's a good thing. Node.js tries to stay small and compact so that it can easily fit into any architecture - This makes Node.js a really good match for third-party process managers and containers/Docker orchestration systems like Kubernetes/Swarm/Mesos.
It would be terrible if Node.js had its own scaling mechanism - It would probably conflict with those of orchestrators which have their own way of doing things.
I think that the Node.js community intentionally decided not to deal with scalability and to offload that concern to external process managers and orchestrators. I think as the Docker/Kubernetes ecosystems evolve, the decisions made by the Node.js community will really start to pay off.
I'm using Node.js with Kubernetes/Docker now to build auto-scalable stacks and it's mind-blowingly excellent.
> Even before Promises, avoiding callback hell was relatively easy
Promises & proper callback definitions don't solve the fundamental issue of forcing code to be structured based on the type of calls being made rather than the logical units of work. If I'm making three database lookups that rely on the results of the previous call, in Node I'm stuck with three callbacks or .then()s or whatever. In a framework like Scala+Spray, I can work entirely with Futures as though they are real objects and return a response with a single onComplete at the very end. In Go, its runtime schedules goroutines around blocking I/O similar to Node's event-loop so the waiting isn't even something the programmer (usually) needs to know about.
Maybe this is the JavaScript Stockholm Syndrome talking, but I've come to really like structuring code based on the type of calls being made. It guides me to extract the synchronous parts and cover them in unit tests. The async parts of my programs are usually talking to a database, a service, or the file system, which are all things that I'd naturally want to exclude or mock in unit tests anyways. I wind up with fast, simple unit tests for the parts of my program that are heavy on branching and calculation.
90% of the time, the programs I'm writing do not logically branch as a result of async calls. I might have to handle errors and abort a process gracefully (which promises and/or the async library do quite elegantly), but those cases feel different than deciding what to do based on the result of an async operation. Because there are few logical branches in these parts of my programs, I get a lot of coverage just by writing one or two integrated tests that exercise all those external systems and verify that they are hooked up correctly.
This idea of a functional core that can be tested with unit tests and an imperative shell that can be tested with integrated tests comes to me from Gary Bernhardt: https://www.destroyallsoftware.com/talks/boundaries. The 90% figure could be dumb luck or inexperience, but so far so good.
Now we have Promises, so this argument has become completely invalid.
Even before Promises, avoiding callback hell was relatively easy - You could just define your functions one above another (even if you used callbacks) - nesting anonymous functions was NEVER necessary in JS - People just did it because they were too lazy to actually declare the function before using them.
>> - Ignoring the fact that JavaScript lacks static typing, its inconsistencies make it a pain even compared to Python or Ruby. It's so bad that entire applications are written in CoffeeScript and transpiled just to avoid JavaScript.
A lot of the main concepts from CoffeeScript have since been introduced as part of ES6. A lot of JS developers never understood the purpose of CoffeeScript - It was merely a gateway to encourage stubborn Python developers to switch JavaScript (and was very successful). A lot of CoffeeScript developers who did not like JS now like it thanks to ES6.
>> - NPM is an absolute clusterfuck. It's a matter of "when," not "if" your application will break because a dependency violated semver. Oh, and good luck developing offline.
The main problem was that some people wrote modules which had very wide dependency version ranges and this caused issues when a dependency would be updated behind the scenes in a non-backwards-compatible way.
npm has always allowed you to specify strict version numbers for your dependencies - I have always followed this approach but a lot of developers didn't. Since this issue came to light, you'll find that most popular modules now use proper semantic versioning or strict version numbers when referring to dependencies.
>> - The over-engineered UI frameworks and tooling have bled over and poured gasoline on the clusterfuck.
I'm sure that some projects are 'over-engineered' but you'd be surprised how often topics like 'the Linux philosophy' comes up in discussions between Node.js developers. Many Node.js developers are really conscious about complexity.
>> - A community with the attention span of a goldfish
I agree with that, the community is all over the place. I think part of the reason is that no consensus has been reached regarding a lot of things. That said, it has settled a lot in recent years. I don't think this is such a bad thing; better be unsure than forcing everyone in the same (wrong) direction.
>> - Scaling is a pain in the ass.
Moreso than Go, but I wouldn't call it a pain in the ass - I think it's a good thing. Node.js tries to stay small and compact so that it can easily fit into any architecture - This makes Node.js a really good match for third-party process managers and containers/Docker orchestration systems like Kubernetes/Swarm/Mesos.
It would be terrible if Node.js had its own scaling mechanism - It would probably conflict with those of orchestrators which have their own way of doing things.
I think that the Node.js community intentionally decided not to deal with scalability and to offload that concern to external process managers and orchestrators. I think as the Docker/Kubernetes ecosystems evolve, the decisions made by the Node.js community will really start to pay off.
I'm using Node.js with Kubernetes/Docker now to build auto-scalable stacks and it's mind-blowingly excellent.