Skip to content

Comment on An open question (rant) about Node.js

Comments

There's no sane possibility of a hybrid for Node.js. The concurrency model in JavaScript is built for one thread [that runs JavaScript]. For servers, in such a model, everything has to be async. A 20ms synchronous operation means 20ms of time that you cannot handle new connections or process others that have sent or received data. It's critical to the success of the platform that almost everyone insist on async-only code. Recall that the asynchronous model has been done before by Twisted and EventMachine, but they have exactly this problem: there's tons of Python and Ruby code out there that's synchronous and if you accidentally use it (even by using a dependency of a dependency), the server falls over. In Node, it's very hard to accidentally use it, by design: you have to write an add-on or use one of the few *Sync functions, which are well-known as red flags in a server.

In terms of why this model is compelling: it scales well, and it's a relatively simple model of synchronization. The 5-line HTTP server on nodejs.org scales to a very large number of requests per second. Having spent a lot of time in the heavily multi-threaded C world (and loving it, btw), it's much harder to shoot yourself in the foot with JS and this model, and the failure modes are usually less severe. (Many C programs use this model as well, including haproxy and much of the kernel.)

It's not all win. Blocked operations are harder to debug. But you can work around things like that, and overall it's a nice environment. And FWIW, once you're familiar with it, the waterfall pattern quickly becomes as second-nature as writing synchronous code. (More complex ones are still tricky, but the basics do become second-nature.)

[edit: I meant that there's no sane possibility of a hybrid between synchronous and asynchronous semantics. That doesn't mean you couldn't use a language feature to make async code look synchronous. That's a much more subjective discussion, but I prefer explicitness over compiler magic.]

This is precisely the fallacy I'm talking about in my top-level comment.

You're conflating asynchronous implementation (which I agree is good), with structuring the code in continuation-passing style (which is bad, for a lot of reasons).

It's all just plumbing, and people demonstrably do it badly if we judge by how well the typical node module deals with unexpected failures. The official node policy on exceptions is that you should let the process die. Not because it's impossible to write exception-safe code, but because almost nobody does it, because it's too hard to do when you're plumbing it all by hand.

Do you have a reference for the "official policy" statement? Any experienced, systems developer, regardless of language, would never think that was a good thing.

In addition, it is not hard to handle the errors due to callbacks. The Node variant of Javascript is basically dynamically typed C and error handling actually follows a very similar pattern to what is standard practice in C. Fault-tolerant systems are hard, period. But since the most important fault-tolerant systems (cars, planes, etc) are all written in C. I do not see why it is "too hard" to build a fault-tolerant server with Node since error handling follows a similar pattern.

Just to be clear, I am not advocating the use of Javascript for automobile software.

Any experienced, systems developer, regardless of language, would never think that was a good thing.

I don't know about node, but it's certainly the Erlang way of doing things. You let the process die and leave a supervisor to deal with the problem.

With the crucial difference that in Erlang, the thing that dies is extremely fine-grained, while in Node it's the entire server. In other words, this is far from the Erlang way of doing things.

Here's where the official API docs say you should always let your process die when you encounter an unhandled exception:

http://nodejs.org/api/domain.html#domain_warning_don_t_ignor...

It's all just plumbing, and people demonstrably do it badly if we judge by how well the typical node module deals with unexpected failures. The official node policy on exceptions is that you should let the process die. Not because it's impossible to write exception-safe code, but because almost nobody does it, because it's too hard to do when you're plumbing it all by hand.

If it needs to be said, that's two (three?) enormous overgeneralizations and one wrong statement. (Can you point to the official policy on all exceptions allowing processes to die? I don't believe that's what the official documentation or best practices have ever advocated.) The vast majority of operational errors in Node.js are Errors but not exceptions, so your statement is also misleading in that way.

Whoa, no. You pass errors as the first argument, but that's about as much convention as there is. There certainly isn't anything official or whatever on what to do with them, here or in any other framework.

I can quote directly from the node documentation:

"Domain error handlers are not a substitute for closing down your process when an error occurs.

By the very nature of how throw works in JavaScript, there is almost never any way to safely "pick up where you left off", without leaking references, or creating some other sort of undefined brittle state.

The safest way to respond to a thrown error is to shut down the process."

Indeed, look at Openresty which is async but written as if the code was blocking, using coroutines.

Don't the coroutines still have to explicitly yield? In other words, not quite written as if the code was blocking?

No, it is all implicit, ie the library function is doing the yield for you, it is invisible in the code you write. Obviously if you are writing new blocking primitives you need to deal with it, but you rarely are as most of these are the socket, timer calls provided by core.

AboutSource Built by g1lg1l

Hackerly is an independent reader for Hacker News, built on the public HN API. Not affiliated with Y Combinator.