Is node.js just a stopgap?

nodejs-logoSomething 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.

28 thoughts on “Is node.js just a stopgap?”

  1. Hey Philip,
    I don’t agree. Most of your concerns have or can been solved through external libraries or better code design.
    Javascript per se – It’s true that you can do weird things because of the way javascript is written. But stick to conventions, design patterns and you don’t really have to worry about this.
    Debugging – Check out node-inspector for debugging. Agree that it’s annoying to have to launch it in browser.
    Callback hell – solved by async
    One core – This just means you should develop your node processes to be stateless. Rely on intermediary caches or the db. Cluster module does kinda suck, but PM2 is a pretty good tool for launching/controlling/managing node.js procsses. Actually saw that you were @ Riverbed. I was solving this exact issue there (before I left).
    Also you can check out my website, backend built 100% in node. http://www.dockumo.com

    1. Thanks for the feedback, codeandcodes.
      I agree that every language can be abused, but it seems particularly easy to do with JavaScript. I consider conventions really important — they help us as engineers communicate with each other — but it’s just too easy to do the wrong thing with JavaScript.
      As for debugging, I know about node-inspector, but no-one writing backend code ever seems to use it. It may the right thing to use but my point is no-one seems to do it. That tells me it’s a poor tool for the job.

      1. It’s true that Javascript can be abused, but abuse usually only results from ignorance. I liked C, endured Java, but love Javascript, primarily because it is isn’t so pedantic. In Java, one has to explicitly express things which in Javascript are implied and this is enough. Sure, it leaves room for abuse, but any savvy coder won’t make those mistakes.
        Javascript is the English of computer languages. It’s impossible to define a robust grammar, but it’s so flexible and easy to say what you want to say.

      2. Hi Phillip, in addition to what was said in that thread and in a few others, let me add my 2 cents:
        1) Javascript – would you add something to undefined or add array to an array in your code? Most likely no. Also knowing how type conversions work in js will be very helpful – https://www.inkling.com/read/javascript-definitive-guide-david-flanagan-6th/chapter-3/type-conversions. Plus like people say – using strict mode in javascript, jshint and other programs will prevent from those issues. People could write c# in sublime text and debug code in runtime, but they like VS because of many static analysis tools which it has baked in.
        2) Debugging – like it was said node-webkit is very useful, you can make it break on exception. Also JetBrains products have awesome debuggers (also check spy.js – tracer).
        3) Callback hell – like it was mentioned take a look at ES6 generators and at following module at particular – https://github.com/tj/co. Note how you can do global try-catch for multiple async calls!
        4) Yes you need to provide architecture for programs with multiple cores support from the very beginning (with Java it is much simpler). But hey, there was a guy from IBM, talking that if you scale to multiple machines, then that is where even in Java you’ll have to rewrite your application code. His point was that with Node you would think about it from the very beginning. My word – for experienced developer it is not an issue, for everyone else that is a way to learn how things are working.

        1. Hi Sergey — thanks.
          Adding an array to array is something I do quite a bit in Python, but that wasn’t the point I was trying to make. The consistency and coherency of a language is often measured at its edges, not in the core. The examples I outlined above seemed very strange to me, and I couldn’t reason about them. It makes one wonder if those behaviours just “fell out” of the language, or followed some explicit design principles.

  2. I’ve got a feeling that node.js is going to become even more ubiquitous, if anything.
    JavaScript: There is a lot of great work in the EcmaScript 6 standard and that is already slowly trickling into Node.js. Compilers and transpilers like CoffeeScript, TypeScript (my current preference) and Traceur are further providing excellent worlds for better JS development experiences.
    Callback Hell: As you discovered, Promises are already today a great way forward. ES6 generators seem to opening up the path to C#-style async/await keyword-style programming to build Promises. Plus there are interesting things going on in the Reactive Programming world (libraries like Rx.js).
    Debugging: This surprises a lot of people, but the Node Tools for Visual Studio makes VS a killer IDE for Node development (and JS development in general). https://nodejstools.codeplex.com/ (Sure it is Visual Studio and not everyone loves VS, but it proves that Node.js debugging can be done and done well.)
    One Core: This reminds me of Python’s Global Interpreter Lock. There’s still not quite a good solution there either. One of these days we’ll all figure out what async should mean in our programming languages.

    1. Max — thanks.
      Saying I should use an IDE to fix my debugging issues won’t work for me. 🙂 Forking out money to Microsoft won’t cut it for many people, not least of which is that we don’t run Windows.
      In fact one of the reasons I prefer JavaScript over Java is that I don’t require an IDE to write it — firing up vim is sufficient for me. The un-ending proliferation of files in Java kills me, and makes an IDE almost required.

  3. Promises were not designed to manage deeply nested callback chains — though they do this. Promises are a way to model problems using functional thinking, declarative modeling vs imperative modeling. This should be clear from the “words” used in the api itself. Promises allow you to comprehend flow in terms of the interactions between values. As values change (asynchronously) the logical “chain” you’ve modeled (declared to exist) is perfected.
    Closures are indeed a powerful thing. As is the ability to pass anonymous functions around as values, to do things like create Promise libraries. JavaScript is a very powerful and interesting language. There are very few “hardcore” JS programmers. There are a lot of intermediate JS programmers who don’t fully understand the language design but benefit from its popularity, its ease of use, and the openness of the community around it (and Node). As this second factor leads them to the first and its core-ness they move past the mental framework this article suggests exists for *all* JS programmers.
    These people are driving the Node community. Node opened up systems programming to these people — people with a deeply felt, natural understanding of asynchronous programming, evented concurrency modeling, closures, functional thinking, and more (such as the “full stack” knowledge of UI/UX, programming with services, etc. that those who built web-based applications exclusively possess). IOW, the concepts that schools don’t prioritize and modern software development does.
    So the comparison with Go is false (though Go is an absolutely fantastic language). JS has a history and social influence, and the reality of its default integration with the browser (as well as many databases, UI kits, and so on) cannot be dismissed. JS is not yet another concurrency modeling language, and its integration with Node creates something that is greater than a sum of parts.

    1. Sandro — thanks.
      I don’t follow why the comparison with Go is false, at least within the scope of this discussion. node.js and Go are both languages with which I’ve found it easy to write programs for the network — both come with a standard library specifically for those types of programs. It just seems to me Go also promotes a more sound programming style, which is something I value highly.

  4. Sure JavaScript has issues. But it won.
    Maybe one day something else will take its place, and I know theres a movement to regarding it as the assembly language of the web. i.e. you write your code in something else like Dart or TypeScript or CoffeeScript.
    Im not aware of anything thats likely to change that in the forseeable future.
    Some amazing work has been done on optimising the hell out of JavaScript with V8.
    I dont expect Node to last forever, but I expect its replacement to code in or compile to JavaScript

  5. Just a few points of contention… node *DOES* use multiple threads, they are part of a pool of threads used for checking background/io processes, and can be used via C/C++ modules.
    The main JavaScript thread is a single-threaded event-loop based system. If you’re writing a server to handle multiple connections break up your workload effectively.
    As to difficulty using cluster/fork, most of said changes you have to do when scaling to multiple systems, which is common enough today.
    Regarding debuging and testing… if testing is too hard, then odds are what you are testing should be broken into smaller modules. A lot of the time lately, I’ll break out modules for single utility methods. I also work to avoid class chains in favor of plain objects, against utility libraries and methods. The implementations tend to be cleaner, you can avoid the use of “this” and testing becomes less complex.
    At this point I’ve worked on a few relatively large projects, where most of the issues were with people trying to use a .Net or Java mindset for solutions written and run against node.js …
    Lastly, beyond Promises generators are on the horizon… the project I’m working on right now is using them… Node 0.11.x is far enough along that it’s pretty stable for greenfield development.

    1. You’ve used Quad Core that is used by IIS but not node. Multiply your node result by 4 since you can start one instance for every core.

  6. Well I tried Go and I just can’t get over its Java-like type system, missing higher-order functions (map, filter etc.) and error handling verbosity.
    Especially the high-order funs…that’s such a useful concept and it’s such a pain programming without it once you get used to it.
    Have you tried Clojure? It seems to have got a lot of things right (including concurrency/parallelism).

    1. Hi Tomas — I have coded some Clojure in the past, but not very much. I wrote some as part of my work on the Loggly pipeline, which used Apache Storm (which is written in Clojure). It’s definitely intriguing.

  7. I guess I’m getting old. JavaScript is not a new thing. It is just the reinvention of Perl. Yes, it is a (much) better language than Perl, but the paradigm is the same: a flexible scripting language that allows you to do almost anything you can do in a more structured language faster: include write unmaintainable nonsense.
    If one is creating applications that are simple-to-moderate in complexity, then I agree that JavaScript is the sensible choice. There’s a reason PHP (bleh) became as ubiquitous as it did; you can do amazing things with it quickly. But when your system grows in complexity, or when your development team grows in size, you quickly realize that the ability to churn out oodles of code is not the only important thing a development team has to do.
    Call me old fashioned, if you like, but I will continue to maintain that development of complex systems demands a language that both empowers the developer – and tells them up front when they’re doing something nonsensical. A strong type system is a minimum requirement. A compiler or some kind of static analysis tool that can tell you when you’re off base is the next thing you need. Until JavaScript, or a scripting language like it, offers these capabilities, I’m not interested in them.
    My $0.02 worth.

    1. Hi Darrell — thanks for your comment. I agree with your sentiment. I much prefer writing system software, software for infrastructure that must be highly reliable, so that is my bias. I’ve come to the conclusion that node. js just doesn’t promote the kind of software engineering practices required by those types of systems.

  8. Thanks for the interesting article – I’ve been developing in and for node.js for a while, but have yet to look at promises closely. I will no doubt research it now.
    Regarding debugging, I will take a risk with a bit of self-publishing, as I have published an npm to instrument your node.js app so you can dynamically decide which functions to track and see their input/output on console: https://www.npmjs.org/package/node-offline-debug.

  9. If you have Javascript front-end app (e.g. using Angular, Ember or React), then if using Node.js for the back-end, and Mongodb (or ElasticSearch or some other JSON storage service), then everything, everywhere is a JSON object. That means you never have to do any marshalling or entity mapping.
    JavaScript being a scripting language, means rapid development, at least for small applications e.g. microservices.
    Incidently, the upcoming ES6 version of JavaScript has some nice language improvements e.g. classes, and also “generators” to improve async programming with something akin to coroutines, i.e. subroutines which can nonpreemptively yield to other coroutines for co-operative tasks. (See http://en.wikipedia.org/wiki/Coroutine#Comparison_with_generators)
    Node.js being Chromium V8, makes it fast. Node being single-threaded, makes it easy, in terms of no worries about thread management, whilst still getting maximal performance, at least on one core. If one needs to utilitise more cores for certain functionality, then consider a load balancer for multiple instances, or a queue service for multiple worker tasks, which is anyway an advisable deconstruction.

  10. I do agree with you. I had my brief usage of Node: a couple of production projects and I wasn’t happy with it, I found the code harder to understand and maintain.
    I have done a lot of front-end JS so am very familiar with async programming. But when doing Node I just got the impression that doing async code all the time was an unnecessary complication.
    Since then I have moved into Go and I am a lot happier with it, you have a nice readable language with goroutines when you need them. I don’t think Node has won or is the future at all.

  11. If the only major point here is you can easily do wrong in javascript then u may have heard of Dark Corners in C.
    https://docs.google.com/presentation/d/1h49gY3TSiayLMXYmRMaAEMl05FaJ-Z6jDOWOz3EsqqQ/preview
    All the other things like single threaded and etc are solved using 12 factor and are recommended for any backend like ROR and python.
    Node is awesome, it has awesome modules, awesome dependency resolution and tries to coop with javascript. And improve on it. U can use Typescript, coffescript and dont know how many others. But javascript is the issue, not NodeJS. I think the title should be change the title to ‘Javascript as a stepping stone to NodeJS’.

Leave a Reply

Your email address will not be published. Required fields are marked *