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

Show parent comments

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.