Go Concurrency vs. RxJS

(code.gdnetwork.co)

42 points | by kermerlerper 19 hours ago ago

47 comments

  • steve_adams_86 12 hours ago ago

    I always found RxJS fairly awkward. I assumed it was a skill issue (and I still think it is), but gradually learned it for personal and professional projects. It's fine. I avoid it now. I think Go is orders of magnitude better in terms of general concurrency tooling, though I know some people straight up hate it.

    I've recently started using Effect, the TypeScript library, and I find it's so much better than RxJS as well. Now that I've gotten the swing of things, I can't imagine going back to RxJS. Although I prefer Go in many ways still, there are things about Effect I actually prefer over tooling in Go as well. And of course, there is no direct comparison (one is a library in a language that's a superset of another language, the other is just a language), but when it comes to solving the problem of concurrency, Effect is great. I recommend it.

    Effect is also a lot more than RxJS. It has schema and data tools, primitives for error handling and control flow, state management, etc. Regardless, where they overlap, I think Effect is much better designed.

    Where Effect (and RxJS) falls apart for me is debugging. I really hope that story improves. The errors in TypeScript are pretty brutal, and actually tracing through calls isn't as intuitive as it appears it will be at times. Weird stuff goes on. Things get convoluted quickly. Somehow that disadvantage hasn't prevented it from becoming a go-to tool for me, so far.

    • whazor 10 hours ago ago

      My biggest issue with RxJS is that it is so easy, that you can easily make pretty bad bugs. In particular, you can have a `mergeMap` or a `switchMap`, and with RxJS they are very similar in API. But if you pick the wrong one, it can lead to weird bugs in your application. However, if you would manually program promises and maybe debounce helpers, you would be more likely to program the correct behavior.

      Still, the easiness RxJS is pretty cool, and if you are an expert at it, you could build complicated applications with very little code. Also Marble Testing is a cool way of testing certain application behaviors.

      Overall, I would recommend searching decent alternatives. Since RxJS is too hard to get right.

  • jchw 14 hours ago ago

    Concurrency in Go is overrated; channels are error-prone and not as flexible as they probably could/should be.

    Though, I honestly find moderately complex Go code far easier to maintain than moderately complex RxJS code, having had both in my career at different times. That said I will admit that sometimes having the RxJS toolkit at your disposal makes solving some problems very easy. It's just that when I construct an RxJS pipeline that does some complex stuff end-to-end, don't expect me to be able to readily explain it a couple months later. It's not write-only... But it gets thick easily.

    One issue I think people underrate that impacts both promises and RxJS is error handling. Exceptions in JS can be a lot of different things, including programming errors like TypeError; but most people will write error handling that doesn't actually distinguish the two cases. In fact it's hard to blame them as this can make for a lot of error-prone boilerplate to deal with, but it's especially annoying as it can hide programming errors in plain sight, making them hard to spot for your observability tooling and harder to debug (it's frequently not a possibility people give much thought to.)

    Then again, on the Go end, I find it hard to try to handle async errors as well, though for totally different reasons. There's lots of good ideas floating around with Go and I even kind of like contexts to a degree, but it feels like there's no cohesive story that binds it all together. I used to use tomb, but I found myself wanting even more than it offered.

    • cyberax 14 hours ago ago

      > Concurrency in Go is overrated; channels are error-prone and not as flexible as they probably could/should be.

      Channels in Go indeed could be better designed and more extensible. However, they are not the only way to use the Go concurrency. There are usual condition variables, mutexes, etc.

      • jchw 7 hours ago ago

        Yep, and frankly I often find myself writing traditional concurrency code in Go. gVisor's checklocks analyzer can be very useful.

    • phplovesong 14 hours ago ago

      I argue CSP is far superior to async/await in almost all cases. In Go errors are explicit and always right there. No weird control flow like you see in javascript.

      • SkiFire13 13 hours ago ago

        CSP and async/await are perpendicular concepts. In fact you can have all combinations of them.

        Personally though I prefer either the actor model or structured concurrency to CSP, depending on the usecase.

        • valenterry 11 hours ago ago

          I like a combination of both: actor model high level (basically, the business needs to understand the purpose/meaning of every actor) and then structured concurrency within each actor.

          I've been trying to convince the gleam folks of that without too much success so far...

      • codethief 6 hours ago ago

        For everyone else who, like me, needs to think for a moment to remember what CSP stands for: https://en.m.wikipedia.org/wiki/Communicating_sequential_pro...

      • glenjamin 12 hours ago ago

        How do you handle errors when reading from a channel?

        • styluss 12 hours ago ago

          data, ok := <-someChan

          If ok is false, the channel is closed and data is a zero value.

          • the_gipsy 10 hours ago ago

            There is no error here, just a closed channel (e.g. finished successfully is indistinguishable).

            You do need to use either two channels (horrible code) or some kind of Result{T,error} wrapper (bad language design).

          • eknkc 12 hours ago ago

            Whenever I used channels in Go, I regretted it at the end. Always have to look up "what happens when I do x and the channel is closed" kind of stuff. Code becomes weird with all the selects and shit.

            I like sync primitives and good old collections.

            • styluss 10 hours ago ago

              Oh absolutely, whenever i use anything, I have to see how to integrate cancellation and now context is everywhere just for this.

              It gets overly verbose because of "simplicity"

            • porridgeraisin 10 hours ago ago

              Yeah, where I worked we rarely used channels directly in business logic. Most of our code looked like "do 5 different time consuming operations, some of them optional i.e can fail" and then combine them all appropriately depending on success/failure so we simply made a BoundedWaitGroup primitive using sync.WaitGroup and a channel to ensure the boundedness that gets used everywhere.

      • dullcrisp 14 hours ago ago

        So you prefer CSP to CPS?

  • phplovesong 14 hours ago ago

    How can these even be compared? Go does concurrency on multiple CPU cores, and javascript one one. Go can so both IO and CPU bound tasks with the SAME way, and in javascript land you really dont do any CPU tasks async at all (makes no sense to do so).

  • sollniss 15 hours ago ago

    The font colors make this unreadable.

    • anonzzzies 15 hours ago ago

      I had the same; you probably have your phone in darkmode.

    • xyst 14 hours ago ago

      Switching to reader mode makes the text readable but code samples a mess. Can’t win

    • steve_adams_86 15 hours ago ago

      If you’re in light mode it helps a bit.

      • xyst 14 hours ago ago

        I’m doom scrolling at midnight. No way I’m going to flashbang myself

        • hu3 4 hours ago ago

          Thanks for the laugh. Me and you both.

    • kopirgan 13 hours ago ago

      Thought it was my phone..

  • cyberax 14 hours ago 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

    • arctek 13 hours ago ago

      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.

      • aporetics 11 hours ago ago

        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?

        • arctek 9 hours ago ago

          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.

    • sph 6 hours ago ago

      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.

  • lenkite 13 hours ago ago

    Compare the runtimes also and how easy it is to debug and maintain RxJS vs Go. The former is a nightmare to maintain.

  • reactor 11 hours ago ago

    Meanwhile, Java just upped the game of virtual thread with https://mail.openjdk.org/pipermail/jdk-dev/2024-October/0094...

  • miiiiiike 16 hours ago ago

    Learning RxJS made me a better programmer faster than anything since the Gang of Four Design Patterns book 20 years ago.

    • roblh 15 hours ago ago

      What did you use it for, specifically? A personal project, or did you work on something that already used it heavily? I’ve played with it, and it’s interesting, but I haven’t quite found a reason to use it yet.

  • raulns86 12 hours ago ago

    One strong point about RxJS is marble testing, I haven't found something similar for async systems with complex scenarios.

  • Alifatisk 10 hours ago ago

    Why was Dart mentioned under the broadcast headline? I didn't quite get it.

  • arunc 13 hours ago ago

    We ditched both and rewrote our services in Kotlin. We never looked back. Coroutines FTW!

    • s6af7ygt 13 hours ago ago

      Can you elaborate a bit more, so us ignorant people may share in your excitement? I've seen many people switch from JVM to Go, but I've not seen a lot of people switch from Go to JVM.

  • 11 hours ago ago
    [deleted]
  • 11 hours ago ago
    [deleted]
  • sunnyque 12 hours ago ago

    guy is using unbuffered channel and wonder why it working slow, lol

    • tankenmate 8 hours ago ago

      yeah, that was my first thought as well; I suspect it is a lack of depth in Go experience.

  • 19 hours ago ago
    [deleted]
  • paulddraper 13 hours ago ago

    RxJS is useful and overused whenever I've seen it.

  • 10 hours ago ago
    [deleted]