You can find the source of the app discussed here at github.com/AnalyticsFire/redux-observaball.
When I first watched the Netflix talk introducing redux-observable to the world, I had only worked with redux-loop for organizing Redux async complexities. I had not used redux-thunk to any significant extent and was only vaguely familiar with redux-saga, which I thought offered more than what most single page applications require.
After more experience with those libraries above, I am convinced that for simple projects, Redux thunk is sufficient and the most lightweight, but any of the above are effective in the hands of a development team that has their API well within their grasp.
That point withstanding, I will say that in terms of handling complex asynchronous functionality, Redux Observable excels (admittedly, my experience with Redux Saga is limited). Take for instance the inevitable question about canceling AJAX requests. I have never had this required on a project, but on projects that require high precision asynchronous behavior, I could imagine how this situation could arise. Redux Observable, which is a Redux middleware for rxjs, can do this right out of the box and the Netflix talk linked to above specifically addresses this situation.
Despite that appeal, if you have an urgent project due soon with sizeable development team, I would not recommend diving into Redux Observable unless at least one member on the team has experience. I first adopted Redux Observable Redux in a React Native app I was developing as sort of side project. The app did not require much more functionality than fetching data from APIs and displaying it. It got the job done without any issue (once I made sure I had
catch statements for all observables), however, Redux Thunk or any of the libraries mentioned above could have done it just as well. In a team context with deadlines, simpler is usually better, so I concluded that for such async responsibilities, Redux Observable is probably overkill.
That observation aside, just from reviewing the rxjs documentation I remained convinced that Redux Observable would have an arsenal of functionality to simplify complex asynchronous interactions. Eager to test this hypothesis, I decided to engineer a problem that would fit the technology. This result is The Redux Observaball phenomenon — it’s put Angry Birds to shame, infuriated the parents of young, procrastinating teens, and spurred Chinese officials to action… Well, definitelly not, but in any case, it demonstrates a situation where Redux Observable excels — apps with high frequency user interactions that result in asynchronous changes to application state.
ReactiveX, RxJs, and Redux Observable
Before getting into the game, it’s worth making some notes on the versatility of the RxJs API that Redux Observable exposes. What is especially appealing is that this style of programming is highly transferable. RxJs is an implementation of ReactiveX, which has implementations in several other programming languages, such as Rx.rb, RxPY and RxGo.
At the heart of all ReactiveX implementations is the concept of an Observable. Similar to a Promise, an Observable is an object that represents an asynchronous operation that emits completion and error notifications to any observers. Observers are any object that implement an interface with
complete methods, which the Observable may call after the Observer has subscribed to the Observable.
Because the Observer implements this simple API, the Observable is able to implement a wide range of asynchronous functionality and properly notify the Observer. You can think of an Observable as a pipeline of code to be executed. As an event passes through the pipeline, they can be filtered, merged with other observables, and mapped to other events that are emitted in sequence to their observers. At each event, the Observable can notify the Observer.
With RxJs, you can create observables from Promises or iterables such as Arrays, or you can create an Observable ofany value. All of this is documented pretty thoroughly in the ReactiveX and RxJs documentation with some great explanatory visualizations. If you want to use Redux Observable, I recommend taking a look at both sources of documentation to understand how RxJs works.
Given that you are reading this blog post, I’m hoping you are familiar with the concepts of Redux and that you could immediately relate to the term subscribed. Quite simply, Redux Observable is a Redux middleware that passes store dispatch events through an Observable pipeline. It takes care of the middleware creation, so you don’t have to worry about the groundwork. Your task is to simply create epics, which are simple functions that accept an instance of ActionsObservable and your Redux store. Most epics start off by filtering the action types they want the epic to observe and calling mergeMap, which will receive the actual action object after it has passed through all reducers — this is a very important mechanical subtlety, do not forget it!
If you wanted to make an API call when a Redux action is dispatched you would do the following:
Before getting into the game, two things should be obvious about Redux Observable now:
- Reducers are called and state is updated before epic code runs.
- Epics do not update state. They are simply a way of listening for actions, executing side effects (eg API calls), and dispatching other actions, which will update state in the reducers.
Now let’s come up with a game that makes a bit better use of the RxJs API.
Redux Observaball — the Rules
As mentioned above, I think redux-observable is a more useful tool when asynchronous logic is somewhat complex. My first thought was to create an epic that somehow implements the functionality of a Rube Goldberg machine. That obviously does not translate to the digital world very well, so I decided to build a graphical game, which would require throttling user clicks and starting and stopping intervals. Here are the game rules I decided to implement:
- The game consists of 10 balls of random mass that fall at some constant acceleration (similar to gravity).
- The user can click below a ball to apply an acceleration opposite to gravity. The user is permitted no more than 10 clicks per second.
- Every click diminishes the user’s strength, but strength is replenished at a diminishing rate over time.
Redux Observaball — the Implementation
In this post I am going to specifically focus on writing epics. There is some non-trivial update logic in the reducersand the D3 visualization is completely separated into its own module that subscribes to the Redux store and reads state to update the SVG canvas.
We can start off just listing the actions we think we’ll need to dispatch:
- applyForce — user applies force to a ball. We may need an additional action if we want to debounce this.
- incrementTime — we update the game board according to gravitational force and energy recuperation.
- start, pause, and reset — all initiated by a user.
- end — end the game programmatically (user has lost).
When thinking of writing epics, it is important to differentiate actions are initiated by a UI event and those that arise as a result of those initial actions.
In our case, there are really only two actions that will result in dispatch of additional actions — applyForce and the incrementTime actions. We’ll only need one epic for each. Let’s break those down.
The appylyForce epic is easy. We just want to receive a user click and then throttle that so user cannot apply a force more than ten times per second. This is simply done. We wait for the
actions.APPLY_FORCE_CLICK action, we apply throttleTime on the epic, and then
map the epic to
actions.APPLY_FORCE, which is created with
In the epic above, we mapped one action to another fairly straightforwardly. The game epic is a bit more complicated. We of course know that we will want to implement some sort of interval. We know Observable’s have an interval, so in this case we will use mergeMap, which merges one observable to another, rather than map, which returns a value for every instance an observable is observed.
Ok great, so now we start an interval every time a user starts the game. However, we obviously do not want this interval to go on forever. When should the interval stop? Well, anytime the game is reset, ends, or pauses. That’s easy with takeUntil;
So now we have an interval with a well defined start and end. Now we have to ask ourselves, what do we want to happen each time the interval is executed? Well, we have an action for
incrementTime. That is a start, but if the user loses we will want to programmatically stop the game, so at each interval we should
incrementTime, but also check to see if the user has lost and, if so, end the game. When emitting multiple actions at the same point in time, we can use merge.
Almost there. Now, we obviously do not want to emit the
end action every at every interval. And come to think of it, we do not want to restart the game if the game is already started and we do not want the user to be able to
applyForce unless the game has started.
Fortunately, Redux Observable passes
store to each of our epics. So at any point, we can call
store.getState to check the state of the game and apply any necessary filters.
That’s it. Doneski. Redux Observable
createEpicMiddleware just takes one epic as an argument, so now we just combine the epics into one to initialize the middleware:
Writing Epic Marble Tests
Now that you’ve seen how simply redux-observable can manage complicated asynchronous actions, it’s worthwhile to take a look at the concept of writing marble tests, which is based on RxJs’s scheduler. Scheduler basically schedules emission of observables in a prescribed sequence through virtual time.
The documentation describes the low level functionality of Scheduler. However, its power really comes to light when using marble diagrams. The first time I saw the marble tests, I thought they looked overly complex and pretentious — they couldn’t possibly be very useful. In the end, they won me over. If you are using Redux Observable I highly recommend becoming familiar with them to test some of you complex asynchronous functionality.
The guidelines linked to above are concise and thorough, so please take a look if you are not already familiar, so you can follow along with the tests we are writing here.
Note, the below tests are written using Mocha, Chai, and Sinon, but marble tests can be written just as easily in any environment.
Let’s start with the
applyForce epic. To recap, our
applyForce epic does the following:
- It filters actions to emit only actions of type
- It will only emit 1 action every 100ms.
- If the game is ongoing, it will emit the
The great thing about marble tests is that they are based on virtual time. So we do not really have to wait for timeouts, intervals, etc. to accurately test our application.
We can begin with a basic epic template (src/shared/redux/epics/test/applyForce.test.js):
Let’s schedule the user to click the applyForce button four times consecutive times and let’s stub
throttleTime to emit the
applyForce action with the same value in the fourth timeslot.
We mock the state to return that the game is ongoing, then we set up our expectation: the epic emits the
applyForce action when
throttleTime passes the value through.
Similarly, if the game is not ongoing, we would expect no action to be emitted:
And just like that, we’ve tested the time throttling as well as filtering for the
applyForce epic. Let's move on to the
game epic. Here we will just test two cases:
- The user starts the game while it is paused and it stops when then
endaction is emitted.
- The epic emits the
endaction if the user loses.
Similar to the way we stubbed
Observable#throttleTime, here we stub
Observable.interval. An interval basically emits values periodically in time. We can mock this with a
--a--b--c marble (src/shared/redux/epics/test/game.test.js):
Now let’s say the user starts the game in the first timeslot. We would expect the epic to emit
incrementTimewhenever the interval emits, yielding
--a--b--c, where a is the action object returned from
actions.incrementTime(). However, we also want to test that the epic terminates the interval when
end is emitted. Therefore, we can emit
end at time slot 7, just after the interval executes for a second time - this would mean that
incrementTime does not emit at the ninth timeslot (ie the third interval execution). Let's set up those expectations (src/shared/redux/epics/test/game.test.js):
Last, let’s test that the epic does not create a second interval when the game is ongoing and emits the
end event when as soon as the user has lost. This is a bit more tricky because we want to allow the test to execute the interval a couple of times before the state changes to "user has lost" (note the use of sinon stub#onCall — src/shared/redux/epics/test/game.test.js).
Again, with this scaffold in place, let’s think through the desired outcome. Even though the user started the game twice, the interval should only start once. The
incrementTime action should emit twice - once for every interval execution - and, because the state will read that the user has lost after the second
incrementTime action has emitted and the corresponding reducers have run, the
end action should emit on the second interval execution as well. We use the
() marble notation to group actions emitted during the timeslot (src/shared/redux/epics/test/game.test.js):
Well, that certainly was not trivial, however, after walking through it a few times, these do become more intuitive and can provide some fairly useful test functionality for your application.
Redux Observable is a very powerful asynchronous Redux library. Once you have a good grasp of its API and Observable behavior, it can abstract away complex logic in a very clear and concise manner. Perhaps just as exciting, the same basic principles and API are available in several other languages that implement ReactiveX. What you learn using Redux Observable you may eventually find useful for server functionality.
That being said, this powerful API is unlikely to be necessary for simple applications that do nothing more than make requests for data from a server. In such cases, simpler libraries such as Redux Thunk may be more appropriate. However, if you can master the concepts of ReactiveX and its API, you will have a highly flexible set of tools to handle complex asynchronous logic.