Redux-Observable Epics vs Redux-Sagas

And Why Do I Care?

Gant Laborde
Red Shift

Firstly, to care about this battle you have to first understand JavaScript and Redux. If not, then you’re probably in the wrong blog article (should have taken a left at the gas station). Those who remain, let’s address the quandary at hand.

In Redux, there’s no prescribed way to handle side-effects. Such a blessing and a curse. Since the jury is out, there have been quite a few popular choices. Major companies like Slack and Netflix have chosen RxJS with Redux-Observable, while the popularity among React Native devs has soared with Redux Sagas.

I’m not going to sugar-coat this article… or even make sense since I’m turning the whole thing into a “fight”. If you’re happy with Thunks, and you’re not looking for a big dose of opinion, turn away now. This article will go where Github stars and brand names cannot. We should evaluate these two popular paths for what fits our own needs. Ladies and gentlemen

… it’s fight night.

In the Red Corner

Redux-Observable

Redux Observable Epics come from a deep history called Reactive programming. In JavaScript we use Reactive programming via RxJS. If you’re completely unfamiliar with RxJS and their observable stream pattern, it’s like a promise on steroids. A promise normally only has a single result, but RxJS observables return a “stream” of results over time.

Break it down:

RxJS — Reactive programming ported to JavaScript. Reactive programming has different names depending on the language the concepts are ported to. This is a great way to take your skills across several platforms.

Screenshot of Reactive lib languages and names

Confused on what observables are? You can play catch-up with Jeremy Fairbank’s awesome video:

Redux-Observable — Applying RxJS observable flow to Redux, and therefore handling async actions in middleware. Actions in -> Actions out styled workflow.

Epics — Observed actions whereby you can use a wide Reactive API from RxJS on Redux via Redux-Observable.

In the Blue Corner

Redux-Saga

Sagas conceptually come from Princeton as a way to handle processes. Using the Saga Process for flow-control and side-effects like async/cache etc. You plug them in as middleware and use them as a sort of “process” that fires at a given time. Sagas take advantage of generator functions to yield their control to your given workflow for redux-inspired events.

Break it down:

ES6 Generators — “Runnable in chunks” JavaScript code. This gives sagas the power to run and stop where needed.

Sagas — Apply a process manager for async, timed, or other non-pure effects so that you can keep application flow logic clean.

WARNING: Graphic Content

It might seem like overkill to implement these over a simple thunk or async/await, and you’re right. These systems are versatile, powerful, and allow an application to expand to complicated business logic. We use these for just about every app we develop at Infinite Red, but we wouldn’t recommend it for any application with a small life-span or possibility of utilizing their benefits.

It looks like both of these contenders have an excellent way of managing centralized-effects and flow-handling with dependency injection. Either would make a fine core driver for your Redux side-effects!

Round 1: Functionality

Okay folks! Here’s where the whole thing starts, Functionality! Both of our competitors are looking armed and ready to unleash their skills. Let’s see how it pans out as they meet in the center of the ring.

Wow! What a display unleashed by Epics! Methods I’ve never even heard before! switchMap, mapMerge, subscribe and plenty more! Observables everywhere! Let’s watch a replay of that last strike!

Look at that chaining! The Epic starts off by looking for a specific action on the action$ stream, then filters to only actions with a specific property, and then enters the switchMap!

It’s no question why Netflix call compares this library to lodash. Looks like Sagas just got a vocabulary lesson, let’s hope that Epics didn’t tire itself out. Because Sagas is still standing… barely.

It looks like Sagas was waiting for an opportunity here, but Epics just didn’t drop their guard. Let’s take a look at the strategy Sagas had.

Not nearly as elegant as Epics. Instead of chaining things they are broken up into independent parts. The wording tradeoff seems even. Good readable takeEvery is blunted by strange new vocabulary for dispatching, like put.

One thing that Sagas has going is that the async/await vibe is present here, and might carry people through the odd but similar generator style of yield/* — Regardless, Sagas comes with a much smaller list of helpful functions as part of the library. Will that be freedom or death? Time will tell.

Round 1 is now in the history books, and it looks most decidedly that functionality goes to Epics. That’s no surprise, considering it’s origins in Reactive programming. Sagas will need to step it up in order to last with the caliber of opponent we’re seeing here today.

Round 2: Ease of Use

Okay it’s Round 2 and you can see Epics are out for blood! Hot off of last round Epics come out swinging with a combo and dispatch TWO actions from one Epic!

OH NOOOO!!!! A complete miss! Epics! What just happened? Let’s go to the replay…

There it is, on line 3! A mistake. We have Observable.of everywhere, and this time it was just too late. Though we went with switchMap for it’s benefits:

We needed to use Rx.Observable.merge on line 3! But there was no fatal error… we’re in production now. This is going to open things up for Sagas to retort!

Here comes the hammer!

WOW! Just wow! Dispatching is easy. Look at 5 Dispatch Combo! Epics are on the ropes. Such a witty attack, but with a hidden flaw. The raw straight-forward manner of Sagas PUNISHES Epics for its mistake.

Round 2 is almost over with a bloodied Epic looking for advice from his corner. Now we saw the pingEpic example form the redux-observable docs, but now Epics will need to come up with something substantial here. Perhaps some of that amazing code we saw in a blurry slide by Netflix? But from the red corner, only SILENCE! We’re not seeing advanced strategies forming here, and that’s what Reactive programming thrives on!

A different story from the Blue corner, though. Look at all these recipes coming from the Sagas coach https://redux-saga.js.org/docs/recipes/ — Even though Sagas aren’t pretty to look at, they sure have a litany of HOW-TOs as Epics takes a licking we hear the bell ending Round 2!

Round 2!!!! What an amazing display! Such strength in both of these competitors tonight! Epics having the reach and speed, but Round 2 definitely goes to the raw determination and brutality of Sagas! Will “slow and steady” win this for Sagas, or will Epics regain control of this bout? We’ll see it all finish here in Round 3.

Round 3: Testability

FINAL ROUND

What an amazing fight this has been so far. Round 3 begins with Testing, and I’ll be honest with you, I haven’t got a clue what we’re about to see.

It looks like Epics is looking for an opportunity to get started with testing. But how do you test a stream? Streams are never actually “Done” are they? This is where Epics trained with Stu Kennedy and a community of testers to come up with an amazing strategy!

By using this helper function you can now call an Epic with your expected dispatches and due to the take command on line 6, this stream will terminate and perform checks! What an amazing function! Kudos to Stu and the many people who’ve helped refine this function!

It looks like Epics are ready to attack! Since this is React Native the attack will be with the popular JEST!

The test came back green! … but wait…. only one action was actually dispatched! OH NO! Ladies and Gentlemen, I don’t believe my eyes! We have a green test, but it should be red! Does anyone know what’s happening here? We’re going to go to our expert in the press-box Testy McTestface!

Testy McTestface here… as you can see we’ve got mistakes all around. JEST by default does not force an assertion. So the callback performed by testEpic only fires when it has seen take(2) and thus never fires the expect statement on line 9!!! I’m not sure if this is due to the complexity of testing streams or if we should file this under JEST, but the correct attack would have been to use the done() callback identified by Stu in his latest testing function for mocha AND the JESTexpect.hasAssertions() like so:

Thanks McTestface! It’s fair to say we saw an Epic Fail. It looks like another landmine has blunted the efforts Epics and given Sagas a chance to answer back, let’s see what he does!

AGAIN, just zero grace. Generator functions are simply rough and dirty. This Saga test is covered in the filth. Look at how we need a stepper function to call next on our generators, and the strange backwards logic of injecting response as a “don’t forget you got this value from above” methodology… but… it’s working.

No magic now, just raw instruction checking. There’s no need to mock the API, because it’s not calling it... it’s just making sure the instructions are to call it. Though we’re in backwards land with the generator, we’ve got no false positives. We’re traversing the possible paths of success and failure, it’s an ugly finish, but a relentless display of coverage is coming from Sagas, over and over again! Epics can’t find a way out! Epics once again on the ropes as Sagas delivers over and over!

DING DING DING! The bell for Round 3 is in, and the bout is over! This has been quite the matchup!

LADIES AND GENTLEMEN… YOUR WINNER

In a decision of two to three, In a stunning upset! Your winner, Redux-Sagas!

WOW, what an amazing upset. Redux-observable (Epics) was the favorite with the large company backings, but the raw down-to-earth force from Sagas combined with flexibility was just too much. Sagas might not be pretty, but its going home victorious.

Epics just showed up with too many foot-guns. Even with classes being offered and a friendly gitter channel filled with smart people, it was too little, too late.

What a Fight!!!! It’s ALL OVER!

OR IS IT?

Disclaimer:

I hate blog articles where “everybody wins” so it’s only fair I picked a winner based on my experience in both of these tools. Both are still great libraries, and should you find the the experience, time, energy, code, to make an argument based on these two libraries that proves otherwise, I’d love to read it!!! Tweet/follow me, and let’s chat 👍

About Gant

Gant Laborde is Chief Technology Strategist at Infinite Red, published author, adjunct professor, public speaker, and mad-scientist in training. Read the writings of Gant and his co-workers in our Red Shift publication. If you’re looking to discuss nerdy tech, he’s all ears. If you’ve got a conference, he’s happy to speak.

View half-witty, half-groan technical tweets with @GantLaborde on Twitter, and follow him on Medium and GitHub. See where he’s speaking next on http://GantLaborde.com/

Responses (7)

Write a response