I'm no zealot, and don't think these are absolute truths by any means but I've worked extensively on enterprise solutions in both C#/.Net and Node.js and can respond to your points below.
1. It turns out the reactor pattern is a good way to build network servers that scale reasonably well with minimal developer effort. In my experience developers tend to avoid writing threaded code in synchronous languages, and threads don't show up very often in run-of-the-mill solutions. To be sure, JavaScript's lack of threads is a blemish, but oddly it forced the community to focus heavily on distributed architectures, and as a result most of the tooling encourages distributed systems, which is a win for some common use cases.
2. Type safety is a hotly debated topic and I don't think you can find resolution on this one. For some people, "weakest of weak typing" is a benefit.
3. Static vs. dynamic, see #2.
4. Do you mean that everything must be enforced by convention instead of static code analysis, compile-time checks and type safety? If so, I think you're making the same point three times.
Erlang is a beautiful language and may be the most correct in some sense, but doesn't look nearly as attractive to the kinds of teams I work on when you start to consider developer availability, library ecosystem, tooling, ISP support, documentation and community.
I think your answers to 2-3 are not real answers. You're not giving an explanation that would convince someone who doesn't share your opinion. You're simply saying that you don't care about there concerns with nothing to demonstrate that your viewpoint is somehow more valid.
I'm just conceding that these argument cannot be won. Tomes have been written and the argument becomes unproductive very quickly. It's like people who believe in God arguing with those who do not. It's not about JavaScript or Node, it's about your entire belief system.
An addendum to #1... node's i/o tooling and other libraries do run multiple threads against a pool, listening for events, then resolving those listeners into the event-loop. The main JS/v8 event-loop is single threaded.
1. This is rarely a problem. You want to be careful about writing code that will take a long time to execute, but most long-running APIs are async which helps a lot.
2. Weak typing is a nice feature for prototyping, but for larger projects, a stronger type system is better at catching bugs. Many JavaScript programmers (myself included) are used to using separate systems to check types for them. My team uses the Closure Compiler, which, along with compiling the JS to a more optimized version, is also happy to check all your types and fails to build if your types don't line up.
3. Again, I believe this is something that you can make sure that the compiler catches. And, of course, anyone can write bad code (in any language); if you're overwriting stuff halfway across the codebase, then that's "bad code" and you should avoid doing that (and during the code review stage, you should be making sure that your coworkers don't do that either).
4. Like for any language, have guidelines for how you write code, and enforce some of your conventions with the compiler and with linting tools. It leads to a more consistent codebase.
All of these are valid points except 1. Node's async IO is one of its strengths. Contrast with Rails, for example, where the standard practice for concurrency is to spawn multiple processes (or, less commonly, threads). How many Rails processes can you fit on one machine? 5-10? Node can handle thousands of concurrent connections, all on a single thread. And when you hit those limits, you can always continue scaling with multiple processes like you would with Rails.
Doing CPU-bound computation in your application server is an anti-pattern. IO-bound computation, however, is where Node excels.
The thing to realize is that pre-emptive multitasking is costly. It is convenient for the programmer (the programmer doesn't need to worry about blocking and locking up the rest of the program), but it comes at a cost. Lightweight "green" threads, or equivalently, Node's evented dispatch mechanism, are a much more efficient use of the CPU. For applications composed of short-lived computations (e.g., < 1ms), it doesn't make sense to interrupt them and context switch. It's more performant to let them complete and then switch. You just have to make sure you aren't doing any CPU-intensive computations in the app server—which you probably shouldn't be doing anyway.
The downside of Node's approach is callback hell. And that's why we have Go.
Cooperative vs. Preemptive multitasking is not the same as Light threads vs. OS threads/processes. Erlang (and also Haskell for example) does preemptive multitasking with light threads. And that's what I'm talking about.
Yeah, preemptive multitasking may be costly. But as you say, when you have lots of users, most of their tasks are sleeping or waiting for timers. That's where preemptive multitasking excels. You can have millions of sleeping tasks and several (at times) doing real work. That's why Erlang is "scalable" and node.js is not. Especially if you need to have state in your workers and if you need them to live longer.
Saying that node's cooperative multitasking excels in IO-bound computation is a clear sign that you haven't tried Erlang.
I have another counter-point for this as a rails developer tinkering with golang recently. I think Go got this correct in many ways. Having light weight goroutines that can scale well; having good IO which does epoll/libuv style wait in the background transparently when you read/write. Easy to understand multiprocessing language in general. I have no idea why it's not taking off in web development though.
> How many Rails processes can you fit on one machine? 5-10?
Wat?!
By the end of the 90's, people started the "10K project", aimed at adjusting Linux and Apache so that a hight-end machine could support 10k simultaneous Apache threads, all doing IO at the same time. And they were successfull.
loxs argument (and as an Elixir guy I fully agree with him) is that those options are all terrible. The Erlang VM (BEAM) is extraordinary in how it solves that problem. It's both preemptive and green threads. It supports SMP and clusters across nodes out of the box. I highly recommend taking a look at BEAM and how different it is from everything else out there.
For enterprise companies the ability to move programmers around from frontend to backend (or demand that they're doing both at the same time) is probably a benefit.
Not a good idea, but that's rarely a hindrance for both enterprisey and startup "human resources" management. Reminds me a bit of how we treated torchbearers and henchmen in D&D...
No, they aren't. The only thing "first-class" in Java is classes. They added lambda expressions in Java 8 but they still must be defined inside of a class method. It is still illegal to define a function outside of a class in Java. And Java methods are still not lexical closures; there are some tricks you can do to sort-of emulate this but closures are not a language feature.
> The only thing "first-class" in Java is classes.
And lo and behold, they have decided that an anonymous class with only one method is equivalent to an anonymous function object, and given us syntactic sugar to invoke it without the class declaration boilerplate.
You're essentially doing the same as complaining that conditionals aren't a Smalltalk language feature.
An anonymous inner class with only one method. The "inner" is important. It still has to exist inside of an explicitly declared class. Also, a variable holding a reference to a lambda function cannot be called as a function (no function pointers).
Contrast with JavaScript, where functions are first-class, can be declared at top level, can be passed as arguments to and returned by other functions, can be called by dereferencing a variable, can be nested, can be partially applied, etc.
The minimal syntactic sugar for anonymous inner classes added in Java 8 doesn't even begin to approach the power of the function support in languages like JavaScript. Language-level support for this stuff matters.
They have exactly the same "power," they're just spelled differently. "func()" doesn't let you do anything that you can't do with "callable.call()". It's trivial to do everything that isn't syntactic sugar (for example, partial application, passing as arguments for abstraction) that you mention in Java, if a bit tedious (and often pointless, since the standard APIs generally weren't designed with that in mind.) _A Little Java, A Few Patterns_ came out in 1998; give it a read.
The fact you put the word "power" in scare quotes indicates to me that you don't understand what it means in this context. Here, have a pg essay: http://www.paulgraham.com/power.html
I think really what happens is Node enables a new type of stack[1]. It doesn't actually really step on anything new the SPA's aren't already doing. Instead it moves to become isomorphic, so initial render is done by Node, but still the data sources, validation, and heavy backend logic all stays behind an API. That API could certainly be Erlang powered.
Also to respond to your points...
1. This I see as the most valid argument, but not a deal-breaker (see Walmart, LinkedIn using Node without problems scaling).
2. Check out Flow by Facebook [2] or TypeScript.
3. Not true with require/modules.
4. It's incredibly easy to include a JSLint file in your repo and as a git hook.
> 1. Cooperative multitasking. Really? In 2014? Hello?
I find this sentiment odd. Obviously it's not ideal for many workloads, but for some things it's the sane option. I work with Tornado in python at work and cooperative multitasking is probably the thing I'm thankful for the most. The server is very IO bound, so it keeps logic seemingly synchronous but hasn't shown any throughput issues thus far.
1. Yeah, Erlang beats JS in that regard without contest. Still, its better than and conditions race deadlocks. (Few mainstream platforms solve this really well and Erlang is one of them).
2,3,4. TypeScript, Flow, PureScript add varying degrees of strictness, checking and purity (from low to high, in that order)
1. Cooperative multitasking. Really? In 2014? Hello?
2. Weakest of weak typing. undefined is not a function? Anyone?
3. Everyone can override everything.
4. Conventions, that's the only way you can build software in JS. Anyone know of a person who doesn't break conventions?
Having programmed in something like Erlang, which IMHO is the sanest technology available today for doing web, JS feels horrible.