Photo by Gabriel Matula on Unsplash

Redux-Observable without Redux

Writing a Stateless Browser Plugin with Redux-Observable

Like Why I Wrote Redux in Lua, I’ve written this article as a story from start to finish so you can follow along with how I came into using Redux-Observable without Redux, and how it solved a business problem specific to the situation I was in.

While this may sound like a complex idea, I highly recommend using Redux-Observable, or a similar style, where applicable because it really helped with code maintainability, figuring out the flow of actions and messages, and made it significantly easier to debug.

History

I started up a side gig back in November which started me out writing a browser plugin. While I’d written a very simple one in the past and did some research beforehand, it wasn’t until I got started that I realized there’s a limited amount of information share It’s weird too since there are so many browser plugins, and it’s just JavaScript, CSS, and HTML for the most part.

Originally, they wanted me to fork another open source project, but after evaluating our options there and playing around with it for a bit, we decided to build one up from scratch.

Starting from Scratch

I’m using to starting every project writing out a build system with configuration files and going from there. Just about every project I’ve ever done has been started this way, but since I was paid by the hour and was limited to working in my freetime, I had to use an existing solution.

After scouring the net for a few hours and finding almost nothing on StackOverflow, uninformative articles, and a few scattered npm projects, I landed on create-react-chrome-extension. Thankfully, I got the green light on using React since writing HTML and vanilla JS would eventually lead me to write some sort of time-saving framework anyway.

While the specifics of this create-react-chrome-extension don’t really matter, it provided me a similar experience to create-react-app and helped me get started. Fast-forward to today, I’m going to be writing my own system, but it was a fantastic starting point :).

Messaging Systems

While Redux-Observable is the best messaging system I’ve used on top of RxJS, it has a huge requirement: Redux. The company I contracted with isn’t a web company so complex JS was a no-no. While Redux is simple, it requires a lot of knowledge and so does RxJS and then Redux-Observable on top of that. I went ahead with functional programming as it is, adding more complexity on top of that would be detrimental for any non-web person to jump right in; even an experienced browser plugin developer would struggle quite a bit.

I was originally scheduled to work for only a couple months so at some point, I’d have to turn this code over to another developer. Since I was working under a tight deadline and wanted to get some deliverables out asap, I started by going straight vanilla JS for the business logic found in background.js .

Sadly, this ended up being a bad choice. Sure, I got something working quick, but browser plugins; at least those using web standards (lookin’ at you Safari), are entirely message-based with a heavy use of callbacks. It was looking more and more like Redux-Observable was the right choice all-along, but as the plugin is effectively stateless — using local storage and passing transformed AJAX responses directly to other plugin-specific functions — it didn’t make sense.

Adding RxJS

The next thing I did was introduce RxJS. It didn’t take long for this mess of complex callback logic to require some sort of transformation layer. If you read my articles on speeding up JavaScript arrays with transducers, this is the plugin that required transducers. The article on Handling Cache and AJAX Race Conditions was also spurred because of this particular transition to RxJS.

Without going into too much detail, I quickly learned using RxJS itself wasn’t enough. I needed to come up with a messaging system on top of it to make the code easier to use. Still, I didn’t want to introduce Redux or Redux-Observable because the weight of those libraries combined with the code complexity they’d introduce would be way too much for this simple browser plugin.

Since RxJS subjects are the gateway to messaging, being both an observer and observable, I created quite a few of them and then loaded up my background.js file with a bunch of these:

someDataFromLocalStorage$

.subscribe() someOtherDataList$

.subscribe(someMessage$)

It quickly became unwieldy trying to figure out where one subject started and the other ended. It was almost as if I was writing imperatively; passing around functions to call after other functions finished processing. This was worse than not using it at all!

To top it off, some APIs use callbacks that can actually take synchronous return values and others send a callback for a response message. While the latter was pretty easy to manage, the former isn’t handled natively by RxJS’s fromEventPattern . I had to create something that sent both the expected args and a synchronous callback function to manage this use case. There’s a lot more complexity to the solution, but it’s entirely black-boxed thanks to RxJS’s composability and should be the topic of a future article.

Redux-Observable without Redux

Now for the good stuff right?

Without Redux-Observable, I’d already created a Redux-compatible actions.js file to help figure out what kind of messages were being sent between various parts of the plugin. This was where I realized I should’ve done something similar for the rest of the app because all those subscriptions being managed in background.js were way too complicated.

Because I already had a Redux-compatible system in one part of the app, I figured it wouldn’t be too hard to write a simplified version of Redux-Observable using my knowledge of its API.

First thing was to create an action$ subject, then write my own ofType by composing over RxJS’s filter operator.

Simple enough:

After a testing this out for a while, I noticed ofType would cause the entire plugin to die if it experienced an error. Why? No clue, but I wasn’t about to let that stop me. I created a separate actionLoggerEpic which I use for local debugging and put my incorrect-action error handler in there. Then I made ofType resilient:

While I could’ve put all that logic in one filter , I chose to use 3 because it was more in-line with each filter doing one simple task. I also found it a lot easier to read.

Now for the good stuff, I needed some way to create a root epic that then subscribed to the action$ subject. Sadly, I did something stupid:

This works, but is totally stupid. I’m not using combineEpics correctly anyway, and this is extremely hard to read. Looking at this now, I’m pretty disappointed in myself. I was like “oh yeah, Redux-Observable has combineEpics and createRootEpic . I’m so smart!”.

I built this solution really quick without checking my other Redux-Observable projects or its source. In fact, I went entirely off my memory. Stupid, I know.

It didn’t take long for me to refactor it into this:

Phew! The previous revision was bothering me so much it was hard to sleep not thinking about it.

All put together, this is what you can use in your own projects:

And that’s a stateless Redux-Observable that doesn’t need Redux. After seeing how simple it was, my mind went “BOOM!”. I couldn’t believe I’d been avoiding Redux-Observable when it was literally this simple to use it.

After rewriting the entire plugin to take advantage of Redux-Observable, it’s a lot easier to follow, and I was able to quickly debug issues by simply logging out every action and throwing errors where things went awry.

Here’s what actionLoggerEpic looks like:

I also added a blacklist, or ignoreList as I called it, for hiding actions that logged too often.

I was also able to modularize everything by action names which fixed a lot of the rabbit-hole debugging that would’ve occurred with the old system. Redux-Observable is like tabbing everything to the left as far as possible, the opposite of callback-hell.

Conclusion

If you’re familiar with Redux-Observable or another messaging system like CQRS, I highly recommend you use these concepts in your async apps; especially ones like browser plugins which are entirely dependent on asynchronously sending messages around.

After having rewritten the plugin around Redux-Observable the last couple weeks, it’s been way easier to get deliverables much faster.

More Reads

If you like what you read, you should also checkout my other articles; especially the ones on Redux: