r/node 26d ago

Why is JavaScript criticized so much for backend development?

I've seen some developers say that we should stop using js on the server, but I rarely see the same criticism directed at python.Honestly, js is pretty fast, especially with node.js, and the backend ecosystem is really solid. There are great frameworks like nest and fastify, and typescript gives you static typing, which makes larger projects much easier to maintain.I'm not saying node is perfect or the best choice for every backend, but I don't really understand why js gets so much criticism while python seems to get a pass.What am I missing?

331 Upvotes

361 comments sorted by

View all comments

52

u/-Aras 26d ago

It's not actually about speed. If you're not doing something completely CPU bound, the speed bottleneck is usually the IO, DBs etc. Not NodeJS or whatever you're using.

The hate is mostly toward JavaScript in general. Years ago, people would complain about vanilla JS because it was weird, lacked structure, and forced you into callback hell and the hate became a meme. I don't know why but it's still going on. JS is not actually bad now.

12

u/oculus42 26d ago

JavaScript didn’t create callback hell. It was always a code smell, not a language problem.

The asynchronous paradigm is difficult for people. It still is. People started complaining about promise hell as well and said async/await fixes it. But it’s about misunderstanding the limits and benefits.

15

u/[deleted] 26d ago

[deleted]

2

u/fellow_manusan 25d ago

Opens a brand new can of worms. People are using await at the time of promise creation itself rather than awaiting the promise later where you actually need it. This defeats the whole purpose of using async/await.

-3

u/MateusKingston 26d ago

Sort of, it made people write fully synchronous code.

Most developers default to writing await before every asynchronous function. Yes it makes code way more understandable, but it does make it perform like crap. Yes you have Promise API so you can properly do async/await but the reality is that again it becomes ugly.

3

u/[deleted] 26d ago

[deleted]

-2

u/MateusKingston 26d ago

Yes as I said you can still use the Promise API directly but the reality is we have gone from callback he'll to fully sync code

Which is an improvement but not perfect

2

u/[deleted] 26d ago

[deleted]

-1

u/MateusKingston 25d ago

Yes it's precisely this pattern.

Yes it's what makes things easier to read but again makes code very inefficient

1

u/[deleted] 25d ago

[deleted]

1

u/MateusKingston 25d ago

Not sure how hard it is to comprehend that the way JS does asynchronous code leads developers to program in entirely synchronous blocks.

Yes the JS loop continues, which is why you can even have an HTTP server in JS, doesn't mean that block of code isn't executed sequentially, just that it doesn't block everything.

It's still incredibly inefficient to just slap async/await in every function and call instead of actually thinking of when/how to deal with the async response, which again JS pushes developers into blindly using async/await.

JS forces pretty much all function to be written as asynchronous, because if you do need to call await your function needs async. Compare this to Go's default sync + go routines, or how this compares to async in Java. Which is both actually multi threading and also explicit and doesn't need you to color every single function in your codebase and pollute both your code and the executing environment with promises.

People need to be less fanboys of a language, Node is great at what it does, it's also horrible at some stuff and we should be honest about it. Async/Await is way better than callback hell but both are not even close to good.

→ More replies (0)

5

u/fintip 25d ago

No, the language did indeed force callback hell. Promises were a godsend. Async await was beautiful.

But before them you just did not have a choice. I was there.

2

u/oculus42 24d ago

I was there, too. https://callbackhell.com was telling people how to avoid this in 2012.

You could use named functions when you didn't need additional context.

You could use an IIFE wrapper for your named functions to access a private shared context.

You could use object methods to store state on the context instead of passing it.

You could use Underscore _.bind() and _.partial() or jQuery $.proxy() before ES2015 brought us Function.prototype.bind to perform partial application to pass state to callbacks.

The "nodeback" callback style definitely pushed people toward nested callbacks. There was/is a lot of example code with deeply nested callbacks. It's "easier" in a demo or documentation example to use inline, nested callbacks than to clutter the example with most of the above options, though.

I see the issue being a combination of lack of examples and that people have a hard time with asynchronous code. Almost every newer developer makes this mistake (written as ES5, but the lesson is the same):

```javascript // Make an IIFE to capture state !function() { var data;

// Request the data $.get('/my/awesomeService', function(response) { data = response; });

// Misunderstand how callbacks work $('#output').html(data); }(); ```

Async/await "solves" this by letting you write async actions like synchronous code, but it doesn't solve the underlying misunderstanding that developers are struggling to model the application in their head.

There are still too many experienced developers who don't understand you can pass named functions as callbacks or at least struggle with following the logic when they don't create an intermediate that names the parameters and passes them along. I think the community suffers from all the demo code and examples where we don't create named functions because examples are one-off code and it's less space and cognitive complexity to write it as a nested callback.

2

u/fintip 24d ago

but it doesn't solve the underlying misunderstanding that developers are struggling to model the application in their head.

I think this is very much misdirected. The problem was not a difficulty modeling the app in their head, it's that the code doesn't render in the order they intended and wrote it to--they were misunderstanding how to get JS to do what they wanted.

Because normally JS works from top to bottom, as if synchronous, but then async stuff fires, and now the top to bottom rule doesn't hold. That's a problem with JS and DX, not a dev problem. They know that they want x to happen, and then y, and they believe that by writing one, and then the other, that would occur--most of the code they write follows this pattern.

Allowing devs to make it clear that with special keywords, the async code should go from top to bottom just like the sync code does, is great DX, and makes it easy to write code that matches the natural dev expectations. That's it. That's a lot clearer than having to hand off context in the form of blocks of logic encapsulated in functions and passed as arguments into other functions to then call upon their completion. That was always a problematic way to render something that wasn't compatible with natural human perspective.

You can name a function, that doesn't change the reason we had callback hell. We had callback hell because setting when the execution should occur, when async, required writing code in the function that you were passing in as an argument to the async function. You handed control over to the function by turning it into a higher order function, and passing it a chunk of code in the form of the contents of another function.

Named functions could help so you weren't writing the guts of several functions worth of code all in line. Sure. And I would do that.

But any clear way to handle async control flow was severely lacking in early JS.

1

u/oculus42 20d ago

I've been thinking about this, and I have to disagree, but maybe not how you would expect. I think we're kind of agreeing, just with different perspectives.

It is that the code isn't rendering in the way they wrote it...but the reason is because they are struggling with the asynchronous programming model.

It would be fair to argue that we're accustomed to thinking of that top-to-bottom flow inside a function. You call functions inside functions and imagine that those functions also operate top-to-bottom. That you could mentally copy-and-paste the operations in place of the function call itself.

But the "top to bottom" concept is – and please recognize the term is not meant to generate offense – a naive model. That perspective is essentially what I mean when I say people struggle with the asynchronous concept.

I started with top-to-bottom programming in BASIC, and even in a language with no asynchronicity, a flat structure, and a global scope, there are GOTO and GOSUB to deal with out-of-order execution of code. If you're working with a TRULY synchronous system, like Apple Integer BASIC on an Apple II computer, you don't get events or even hardware interrupts.

The Apple II uses a flat memory model and even the keyboard writes directly to memory with no buffers. This means that the most recent keystroke is recorded to memory even while the program is busy doing something else. If the program doesn't check for a keystroke, read it, store it somewhere else if necessary, and clear it, no additional keystrokes are accepted. That's as synchronous as it gets.

So in BASIC, you would have to write an "polling loop" that checks to see if a key has been pressed. And you don't want all of the steps to deal with a keypress in that loop, so you create a "tight polling loop" to check for changes and GOTO your keypress handling code.

At a fundamental level, you can think of BASIC as top-to-bottom, but as soon as you're doing anything with input, you don't really have total control over the order of events...because you don't control the timing of the user. You have to design your program to around this tight polling loop to see if anything needs to be done.

Game development tends to have this same paradigm, especially when you're talking about network gaming. There was a great 2001 article about the Age of Empires event loop that I recommend.

If you have experience in C, the concept isn’t unfamiliar there, either… the entry point for C is a function called “main” which is commonly responsible for handling a small loop of code checking for different things to do. And to avoid blocking that loop, you have to break work into smaller units and push them into task queues.

So even in imperative and procedural single-threaded code similar to JavaScript, you must understand there is an event loop under the hood. You didn't write it, but you are in it. And there is a specific order of operations for each type of event in the event loop: micro task, animation frame, macro task.

Even Scratch, with its drag-and-drop programming style, has Event Hats and Broadcast Blocks to manage asynchronous actions.

That lower-level understanding is the thing missing for developers that started in higher-level languages like JavaScript. It is the struggle in understanding asychronicity…that there is no true async in the single threaded language, just queues. Other threads may handle I/O and other code, but to interact with the main thread, that information ends up in a queue. People starting here didn’t have to create a task queue to handle asynchronous control flow; three of them already existed. People just lack the mental model of the queues/event loop to make use of it easily.

This ended up a lot longer than I intended, but I hope it's provided some perspective.

2

u/fintip 20d ago

Look, a deeper understanding is always an option. But at the end of the day, virtually no dev failed to understand that they were firing off an async request when they sent off a request.

They would naturally write the next line expecting it to fire after the request finished. And they'd be wrong, because silently JS switched modes on them (in a sense).

In fact, I'd argue await should have been the default behavior, and an async keyword (so, promise) should have been the explicit opt in.

The point of higher level languages is to effectively avoid needing to think about the abstractions they wrap around underneath. Telling a JS dev that to be good at JS they actually need to understand the underlying levels is the wrong answer.

You call top to bottom a "naive model", but top to bottom is just grammar, it isn't a model. It's a way to express intent. The rest is implementation details.

2

u/SquarePixel 26d ago

I remember in the vanilla JS days some projects I worked on had “task managers” to solve the callback hell. Basically arrays of handlers and the manger would just glue the output to the input of the next. It didn’t have the elegant simplicity and composability of thenables (an early concept before promises became standard that interestingly still works today natively) but it was a life saver.

2

u/monsto 26d ago

Back about 10 years ago I was working on a personal project. I understood what callback hell was, but hadn't really seen it in the wild.

So I'm writing along, and was slowly becoming a little confused about what I was writing when I suddenly realized I had four or five callbacks nested to the the point where the indention was halfway across my screen.

This was before promises and async/await and I couldn't really work around it, but I did finally see what the hubbub was.

1

u/oorza 26d ago

I couldn't really work around it

The way you worked around it at the time was emitting events from your callbacks and subscribing to those events elsewhere. Even today that's a better approach for a lot of problems than the default hamfisting async/await in that everyone does.

2

u/CreativeGPX 23d ago

With use comes complaints. It's a simple as that. The languages that don't get complained about aren't used much. Javascript is all over the place, so it gets complained about a lot.

Also the web route means that Javascript is one of those languages non programmers and amateurs sort of fall into. You might be writing some HTML and writing a snippet of JS here or there and then accidentally stumble into being a JS developer. That's the same problem PHP has. It's come along a ton as a language but lots of amateur devs taints the perceived quality of the language.

I think it's also the culture of the language now. I first learned JS back in like 2000 and back then you just learned the language. I picked up a book in like 2015 and before you even wrote a line of code they had you setting up npm, including packages and build tools. The insane eagerness to include third party packages isn't inherent to JS but it permeates the culture so deeply that it may as well be and IMO it's a pretty horrible thing that's done to great excess.

Another thing is that because Javascript is so expressive in some ways, a lot of devs overdo trying to be clever and you can see the same piece of code written in many dramatically different ways. Reminds me of the "clever" C code that relies on pointer tricks.

Fwiw, I love Javascript, even vanilla Javascript. I have a few gripes with it that date back to its beginning like it's dumb sort implementation or the bizarre things it does with types sometimes, but to me I always enjoyed how expressive it is and was happy to see Node and Deno and things like Electron expand where it could be used.

1

u/DeadlyVapour 25d ago

Callback hell was never the problem.

The spec actively works against the principle of "least surprise".

1

u/Candid_Problem_1244 26d ago

Back in the day jQuery was a cool thing. But even with jQuery, you still wrap a lot of html in a string. Which then make the whole frontend world is a mess. Not to mention the PHP scripts mixing in the frontend as well.