Hacker News new | ask | show | jobs
by cyberax 629 days ago
JS concurrency is crap. It should be shot and buried in a lead coffin.

Debugging async code is pure hell. With Go, you have a normal debugger that can be used to step over the code. You can get normal stack traces for all threads if needed. There is a race detector that can catch most of unsynchronized object access.

With JS? You're on your fucking own. You can't find out the overall state of the system ("the list of all threads"), without getting deep into the guts of React or whatever framework you're using. Debugger is useless, as each `await` call drops you into the event loop. So pretty much every complicated non-trivial JS app ends up with _tons_ of race conditions, by depending on the order of async functions finishing.

Speaking of race conditions. Coming from classic multithreading and Go, I tried to use `Promise.race` to simulate the `select` statement. Turns out that in JS it is actually useless because it LEAKS MEMORY BY DESIGN: https://github.com/nodejs/node/issues/17469

Back to the article. It uses pre-generics Go. In more modern Go, you can use something like Conc to reduce the boilerplate: https://github.com/sourcegraph/conc?tab=readme-ov-file

2 comments

It's also unintuitive in the sense that if you are racing you usually want to discard/stop the other Promises that didn't finish in time. But the only real way to do this is to pass through an AbortController down into whatever networking/IO task (and your own code for that matter) is happening to "cancel" it that way. Otherwise you just end up with the result of the fastest Promise and the rest will continue when they get a chance.
I thought the point of using observables over promises is that they have cancellation, and presumably an implementation of race would cancel any slower request (i.e., handle the abort controller automatically)? Is that not the case?
My comment was just on the standard behavior. Not sure when it comes to observables, probably comes down to how you are implementing it. But in general it's impossible to cancel a hanging Promise, you have to have some kind of signalling mechanism that either throws so it rejects or otherwise resolves the Promise chain.
Jesus I wish I could upvote you twice. I am investigating a memory leak due to async code, and there is no way to track it down the cause easily. Even the memory inspectors of Chrome and Firefox are unable to deal with it: they tell me the app is not leaking, the heap size is constant, yet the process RSS itself grows until OOM fires - forcing a manual GC cleans it all so it's not even a leak, but just a terrible interaction between the browser generational GC heuristic and async code.

The workaround? Chuck async code in the bin, in my case replace the await fetch() calls with synchronous XMLHttpRequest requests. Problem solved, even if MDN tries to discourage one to use sync XHR because fetch is the new hotness.

I wish I wasn't so broke that I have to accept JS contract work to pay the bills. JavaScript, and its async/await implementation should be thrown into the flaming sun.