* 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
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.
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.
https://levelup.gitconnected.com/aws-lambda-cold-start-langu...
These days, I’m mostly paid to write web applications. So, I default to JavaScript. That said, there are plenty of problems where JavaScript is such a poor fit that I’ll reach for something else.
With npm, there are lots of options... generally if one package doesn't provide an interface I like, or performs poorly, or just has odd dependencies it shouldn't need, there's another that's probably closer to what I want. Worst case, if there's a smaller change, I fork the project on github, update to a scoped name, and publish my fork.
It's not always pretty. That said, I tend to be considerably more productive with it.
I've also worked a lot with C#, and am recently learning Rust... I feel the first version of most things should be done in a scripted language, and JS/Node is just as valid as any other option in the space.
- async-everything, not a blocking language where anything async relegates you to a subecosystem like you have it in basically every other language from Java to Rust to Python.
- simple stdlib Promise that everything uses. no fragmentation between competing byob futures and byob abstractions.
- async/await.
- single-threaded making it ideal for i/o workloads, a crawler being the perfect example.
For these reasons I think it's one of the best languages. Certainly didn't used to be this way.
Edit: to respond to the GP, I think sharing a language between front end and back end can enable people on either side go full stack with more ease. That's a benefit I guess, but not one I considered of particular value.
I would love to see JavaScript turn into a less verbose, more consistent and less golf-oriented language.
Sometimes I can remove dependencies and code because they are replaced by the new feature of the runtime, thus the maintenance becomes easier.
My application (https://operatr.io/) is Clojure (JVM / Back-end), Clojurescript (Browser / Front-end), Clojurescript (AWS Lambda) - there's enormous leverage is using one language in all cases.
I have at least one function that exists in all three environments, but more than that I have one common language for delivery.
Node is not a web scripting framework, though the fact that JS is used as the main language for web front end is a big part of the attraction; as it lets frontend and backend share code and be served by the same language competency.
This all seems to be pretty useless indirection to me.
If you have one or two lambda functions, then definitely this may be overkill, like you said just call a few functions. But as your system matures and grows, these few functions become a few modules, then your few modules become libraries, so on and so forth. You might want to consider this pattern before it's unclear what exactly is interacting with your requests and responses and in what order.
I think it is far enough for my needs, it is nice to use no framework other than the one provided by AWS.
Are we talking about the same JS here? Because the JS I know is a dumpster fire of a language.
result = somePromise() // run while the next promises are resolving
pages = await Promise.map(url, url => crawlUrl(url), {
concurrency: 4
})
allResults = await Promise.all([result, pages])
is some of the simplest concurrency code there is. You can keep chucking in more async logic and it doesn't get much more difficult to understand and doesn't introduce much more code.If you want verbosity, look at Go's equivalent (wait groups) or, god forbid, any concurrency management in Swift.
The only problem you run into with callbacks imo are event-emitters like streams which you need to actually understand. Though I don't think this is any more trivial in other languages with evented/callback APIs like Java and Rust, I think reasoning about event callbacks is always harder for humans but a useful construct and necessary evil in evented code.
That sounds wasteful, and imo makes Cloudflare's Serverless tech superior for strictly network-io bound workloads. Lambda, to be fair, supports way many event triggers and all sorts of runtime and user-space constructs, but still manages warm start times <10ms which is really impressive.
AWS Lambda uses their Firecracker micro-vm tech which supports more runtimes, environments and triggers than just Node and also runs container workloads. [2]
Very async.
https://benchmarksgame-team.pages.debian.net/benchmarksgame/...
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.
But even with idiomatic code the performance is quite good - the JITs are very high quality.
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.
I'm mulling contributing N-API support to the libraries... but I haven't even done any research or planning work yet.
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 removed the unnecessary middleware bloat (pug html renderer middleware for an API server, really? body-parser and form parser even for endpoints where it's not being used?) and switched to standard pg instead of pg-promise (standard pg also supports promises, pg-promise hasn't been needed for quite a while now)
The performance went from 600req/s to 5500 req/s on the db benchmark, 9x improvement with 10 minutes of work. I think thats a pretty damning result for the tech-empower framework benchmarks quality, at least when it comes to node. This is just standard libraries and practices, not even hacks like replacing the built in url parser with fast-url-parser.
Still better than a false impression that nodejs is somehow slow.
They really need some QC though.
you can run ./tfb --test express-postgres and compare.
If you can't wait for all the tests to complete, a representative one can be obtained more quickly by running
./tfb --test express-postgres --type db --query-levels 10 --concurrency-levels 128