Hacker News new | ask | show | jobs
by eric_b 3294 days ago
I worked on a fairly large and high traffic React app. It had some complicated async flows, so the team moved from redux-thunk to sagas.

The claims that testability is improved do not ring true to me. Some of those tests were just awful to write and maintain. I'm sure someone will say "you were doing it wrong" but there were many folks on the team following advice from the maintainers themselves on Discord. General understanding of Sagas by less seasoned developers was also worse than trying to teach them promises.

After the codebase was converted, the team generally concurred that sagas were a waste of time and effort, especially with async/await gaining steam. They also felt that maintainability and debug-ability suffered with sagas. Reasoning about what code runs next is easy, until it's not.

YMMV, but I've tried all the async patterns, and I think sticking with async/await and promises is probably the best bet these days. The code might not "look" as pretty - but aesthetics should hardly be the main goal.

5 comments

> After the codebase was converted, the team generally concurred that sagas were a waste of time and effort, especially with async/await gaining steam.

To be fair, I'd almost certainly recommend async/await for simpler use cases- in particular, an application in which API calls are the only async behavior. However, in our situation, redux-saga turned out to work pretty well. We needed to coordinate some relatively complex UI flows (the user would draw on a map, then the app would POST the coords to the server). Before introducing redux-saga, we were starting to get lost in a daisy-chain of Redux actions.

> Reasoning about what code runs next is easy, until it's not.

This was the general idea behind "navigationSaga"- to set things up so there's as little as possible happening at a given time.

> The claims that testability is improved do not ring true to me. Some of those tests were just awful to write and maintain.

I dunno. On one hand, I wish there were better tools for testing sagas, but on the other, testing long chains of Promises has caused me all kinds of headaches in the past, as well. I find that it's easier for me to keep responsibilities separate when using sagas than it is using promises or suchlike.

And for what it's worth, I'm not trying to bill sagas as a be-all-end-all solution for all life's problems. :) I was just trying to sorta explicate an approach that worked for us.

It sounds like you guys made the right moves - trying promises first until that fell apart, and then carefully picking a new path forward.

My comment was mainly intended as a "cautionary tale" for those folks who get really excited about the latest and greatest, and don't apply that base level of skepticism to new tech.

Any claims about scalability or testability mean nothing without concrete code examples to back them up. Any tool that makes claims such as these without a sufficiently complex example in the docs, alongside a baseline implementation for comparison, is a huge red flag IMO.
We have been trying elm, and while it has still poor support for "web API", maintaining and growing an elm app has been the most pleasurable and easy thing that happened to us in a long time.
Interesting. What specific concerns or pain points did you experience? Any examples of "reasoning about what code runs next is easy, until it's not" ?
The put/call/takeLatest/takeEvery effects, coupled with all the yields made things impossible to grok for the less seasoned folks. Even for me, having seen some similar things before with other libraries in other languages, it's just not a pattern that my brain wraps itself easily around. There is so much indirection between the sagas and reducers and the effects. Like, I just want to see the code that actually does all the work. I understand the sagas "look" easy, but that's just because all the good stuff is hidden away. I like to see easily where the rubber meets the road, so to speak.

The main selling point was "look how easy this code is to read, it's async but you can read it procedurally". This is mostly true for simple sagas, but the advanced flows were challenging to read, especially trying to remember what each effect did, and what "real" code it called under the covers.

Thinking back on that code, had async/await been used - it would have been simpler to work on and understand, and you wouldn't have carried the weight of a third party library with somewhat esoteric ergonomics.

Edit: It's been several months since I worked on that code, and it's all Angular 2,4,a billion all the time now, so I apologize for limited specifics. In general I appreciate that people are trying new async things (CSP, sagas, observables etc) but they all just feel like a fad to me. Obviously use the right tool for the job, but most of the time I think promises should be the way to go unless there's a super compelling reason not to.

My gripe about sagas is that it mostly reimplement observables-like APIs on top of generators. Observables do take a bit to learn, but once you learnt them, the knowledge is more or less reusable across platforms (even if it's a little different, you can do Go, Scala/Akka, Elixir, and any language that has an Rx implementation).

With Sagas, while the upfront cost and the gain are in the same ballpark for Redux apps, that's basically the only place you'll use the investment. You also cannot leverage the collective knowledge and patterns of the tens of thousands of engineers from other platforms.

That makes it a lot less palatable to me, even though they are easier on the eyes at first, especially for people with mostly procedural programming backgrounds (which is the more common case).

Unlike the initial statement, I do think they're easier to test than even Observables/Promises, because generators make it so easy to inject results and dependencies anywhere in a complex flow. But marble tests aren't bad either.

> There is so much indirection between the sagas and reducers and the effects. Like, I just want to see the code that actually does all the work. I understand the sagas "look" easy, but that's just because all the good stuff is hidden away. I like to see easily where the rubber meets the road, so to speak.

So do I, which is why I dug into it a bit deeper before using it on a project. This article is actually the third in a series (see [1] and [2]), and the first one goes into a little bit of detail about how things work behind the scenes.

> The put/call/takeLatest/takeEvery effects, coupled with all the yields made things impossible to grok for the less seasoned folks.

I definitely feel you on this, and it's _very_ easy to let things get out of hand. I'm very much _not_ a fan of nesting an anonymous generator function inside takeEvery/takeLatest; it's just too easy to start accessing variables from the enclosing scope and then everything gets totally FUBARed.

> It's been several months since I worked on that code, and it's all Angular 2,4,a billion all the time now, so I apologize for limited specifics. In general I appreciate that people are trying new async things (CSP, sagas, observables etc) but they all just feel like a fad to me. Obviously use the right tool for the job, but most of the time I think promises should be the way to go unless there's a super compelling reason not to.

This is totally fair, and I definitely agree that apps should start with simple tools and only use power tools when they're called for. As I mentioned in my other comment, we started out with something like Promises, and moved to redux-saga when that approach was starting to get cumbersome.

1: http://formidable.com/blog/2017/javascript-power-tools-redux...

2: http://formidable.com/blog/2017/composition-patterns-in-redu...

> After the codebase was converted, the team generally concurred that sagas were a waste of time and effort, especially with async/await gaining steam.

Aren't sagas and async/await completely orthogonal issues or am I missing something?