Something just doesn’t feel right about node.js.

After coding in it for almost a year, it’s been fun, but I’ve decided it’s just a waypoint to somewhere better.

But I can write programs for the network really easily!

There is no doubt that node.js got some things right. Some while ago I wrote a very high-performance Syslog Collector that could process and parse 250,000 log messages a second. It could do this with thousands of active simultaneous TCP connections. It also supported UDP, HTTP, and SSL. It was written in C++ and used the Boost ASIO framework. It almost never crashed, and didn’t leak memory. But it took 5 months to write (and then re-write), test, and deploy. So learning how to write similar programs in node was a real breakthrough when it came to speed-of-development.

Need a HTTP server? require the http module. Ditto the net module to bring up a socket server. And node is an event-driven framework, promising performance superior to the old way of a thread-per-connection model. node.js became popular in a way Twisted never seemed to.

What’s wrong with node.js?

JavaScript

Let’s start with JavaScript — I like its use of closures, but something about the language is fishy. Even the hardcore JavaScript programmers I know constantly joke about it. All smirk about how JavaScript the Good Parts is a pretty small book — and compared to a book on the full language it’s even funnier. And then you’ve got the following, which doesn’t raise an error at the node REPL:

undefined=42



Or how about this, using the node.js REPL?

> [] + []

''



Why doesn’t 1 empty array plus another empty array equal an empty array? It does in Python.

Or this?

> [] + [] * 5

'0'



It’s very difficult to reason consistently why JavaScript does this. It’s like a language for a 100 billion monkeys. Since so much is acceptable at the REPL, type anything at all and you might get a real program.

At least JavaScript does not have as high a type-to-thought ratio as Java. But at least Java forces you to think where your code will be 2-months out.

Debugging

Everywhere I look node.js developers are debugging programs in a really primitive manner, and constantly express frustration with the tools available to them. Tracking down why exceptions are thrown in callbacks, and in unit test frameworks like Mocha and Chai, is often a nightmare. Hours are spent trying to work out why programs are failing. Liberal use of console.log() seems to be the only tool most developers even use, when it comes to debugging issues.

Callback Hell

Most programmers, within 36-hours of first programming in node, discover callback hell. Entire websites have been dedicated to explaining how to avoid this problem. It’s deeply ironic that the asynchronous, event-driven nature of node, which gives it so much performance advantage, results in such unsightly and shaky code.

But we’ve got Promises, you say! I agree. If it wasn’t for modules like Bluebird I think most node programmers would have given up by now. I really like working with Promises — it introduces an aesthetic back into the code. Chaining a sequence of Promises is a subtle way to program a computer and a consciousness-raising exercise. In fact Promises are so much fun that it makes you forget that they only exist to fix the really, really bad problem of callback hell — a problem which, I suspect, seriously threatened the viability of writing serious programs in node.

I’ll take one core please, but just one

node.js always runs in a single thread. This makes it much easier to deal with data that is shared across functions and modules. It doesn’t mean there are no race conditions, but it does mean you can be sure that your code won’t be interrupted while modifying an object. But once your node program maxes out a single CPU core, where can you go? You can’t make use of all the extra processing power that exists on multi-core machines. You’re stuck. Sure, there is Cluster Node, but it’s experimental and significant chunks of your program may need a re-write to use it. And your overall program design may never have considered that you may need more than one process on the same machine to cope with the load.

Where to go next?

Writing high-performance programs for the network and Distributed Systems has now become a extremely important element of contemporary programming. And frameworks which minimize context switching — like node does — allow us to build high-performance servers. But better alternatives to node.js exist.

An obvious candidate is Go. It comes with a robust standard library, built for implementing programs for the network, so makes coding servers as easy as node. Goroutines allow the programmer to forget about callbacks and code in the way you, the programmer, naturally think — in a linear manner. And underneath, Goroutines are really lightweight so the cost of the context switch is much less than an OS thread — and it was the OS thread context switch that node.js was built to avoid.

Go can also take advantage of multiple cores without too much trouble. It’s built into the language. I’ve written a couple of significant programs in Go, and it’s been such a better experience than writing node code.

Don’t take my word for it

My experience in node and JavaScript is nowhere near as deep as it is in C, C++, or Python. But my intuition — and others‘ (and others‘, and even others‘) — tells me node.js is only a stepping-stone to a better way of writing programs for the network. It’s done a lot right — and I’m really glad I’ve got it in my toolbox — but there has got to be a better way.