Hacker News new | ask | show | jobs
by spion 2313 days ago
Main strengths:

* The ecosystem is pretty strong and constantly evolving. Lots of companies and individuals are putting a serious amount of work into making high-quality tooling and libraries. (Although with that also comes a lot of lower quality stuff and its sometimes hard to tell)

* The language isn't owned by any single company and is well specified

* TypeScript provides a powerful and flexible type system with advanced dependant-types like features. Lets you gradually evolve from a messy JS prototype to well-organized code with fairly strict type checking

* Fastest dynamic language. The performance is also quite comparable to statically typed GCed languages. (For example, you can probably get to 50%-90% of Java performance on most single-threaded workloads)

* wasm support in the runtime a promising escape hatch to the integration with other languages should that become needed. (Similarly not owned by a single company and well-specified)

* sharing code and types between client and server, including interfaces, validation and data models.

* particularly well suited to handling heterogeneous structured data due to how cheap it is to define new object types (even in typescript)

* Async-IO-first (in fact, async-only IO for the most part)

Main weaknesses:

* Poor standard library with an anemic set of classes, anemic set of implementable protocols/interfaces (more stuff like Symbol.asyncIterable needed) and lacking convenience functions. E.g. see https://api.dart.dev/stable/2.7.1/dart-async/Stream-class.ht... and compare with... well nothing in the language! The official node streams have a terrible API.

* Combining lack of both protocols and standard library leads to pretty bad userspace library fragmentation. (What is the go-to stream library?)

* Restricted data sharing between threads (only SharedArrayBuffer) makes it quite limited for multi-threaded problems.

* (mainly TypeScript) - Insufficient reflection capabilities

* Rigid (non-configurable) node_modules resolution algorithm accepted as standard limits flexibility when organizing projects

Mythical weaknesses that don't matter that much:

* Implicit conversions - largely irrelevant since TypeScript

* DOM/Browser related weirdness - often attributed to the language but actually problems with browser APIs

2 comments

> The performance is also quite comparable to statically typed GCed languages. (For example, you can probably get to 50%-90% of Java performance on most single-threaded workloads)

Citation needed, please. I disagree with this. I recently was able to achieve a massive speedup in a Node application through writing a native C++ module... and implementing Garbage Collection is required for N-API.

Excellent source; thanks!

It’s probably worth clarifying that Node is ⅓ the speed of Java in some of these cases and that the Node implementations use both fixed size typed arrays as well as worker threads, features that aren’t common practice in most Node programs.

I wanted to illustrate the fairly "wide dynamic range" available. A lot of typed, GCed languages require significant effort to write highly optimized code anyway, and the optimized code rarely looks like the idiomatic one.

But even with idiomatic code the performance is quite good - the JITs are very high quality.

I didn't downvote you – in fact, I upvoted you. But I should also make the point that worker threads do not support importing native Node modules that were built directly on the V8 API. For example, most popular sqlite libraries (better-sqlite3, sqlite3) are not usable with worker threads.

I'm mulling contributing N-API support to the libraries... but I haven't even done any research or planning work yet.

An interesting blog post on how that might be done: https://mrale.ph/blog/2018/02/03/maybe-you-dont-need-rust-to...
That was amazing to re-read, thanks for sharing. news.yc discussion back on tfa back in the day is a pretty good read, too: https://news.ycombinator.com/item?id=16413917
For those situations where the GC becomes a performance (or more likely memory usage) problem, it seems reasonable that the built-in WASM support of most engines will be able to provide an adequate performance (its already getting quite close): https://www.usenix.org/conference/atc19/presentation/jangda
Anything CPU-bound is a very bad fit for node. But most web services are IO-bound, and Node is excellent for them.
> But most web services are IO-bound

Node's still relatively slow for those workloads.

https://www.techempower.com/benchmarks/#section=data-r18&hw=...

See how far down in each section you have to scroll to find node, even for workloads that are purely "accept a request and respond with a static string". You'll see lots of Java and Go on your way down.

And most services will have far more compute than just shoving bytes in between services. There's request parsing, response encoding, usually at least a tiny bit of data manipulation.

This benchmark mainly measures the performance of the HTTP parsing and database libraries, often in a suboptimal default configuration. In node land, they're admittedly not amazing, but nothing about the language prevents them from being better.

For example, for a long time, the only reason that node was slow on these benchmarks was the built-in URL parser. Replacing it with this carefully written module https://www.npmjs.com/package/fast-url-parser resulted in 2x improvements on the benchmark. I haven't looked closely at the situation nowadays but I imagine its still quite similar with lots of low hanging fruit lying around and stalled due to backward-compatibility concerns.

For proof find "es4x" in the benchmark list, which basically replaces the entire stack of HTTP parsing and database libraries with the ones from vert.x and runs JS on Graal, even though Graal is currently at least 2.5x slower than V8 in terms of JS performance: https://github.com/graalvm/graaljs/issues/74

Node core (and the libraries around it) has unfortunately stalled in the "good enough" zone for quite a while. The good bit is that they stay in the good-enough zone after adding your own code.

I think your point is actually just proving something important that I ended my initial post with - that there is almost never a "pure IO" workload, but that compute is actually an extremely important part of any service. Given node's concurrency model it's even more important, as compute can block other operations.
I agree, although for server side rendering of web frontends, it is still the best choice even though producing vdom and rendering it to string is usually cpu bound.
> (mainly TypeScript) - Insufficient reflection capabilities

You can always use the compiler api to extract type information. Sure, a bit tricky but doable.

The compiler api exposes a lot of awesome things, I'm suprised there aren't that many tools that use it.