Handling Asynchronous Actions in Redux
An in-depth look at the most popular approaches
Redux is a beautiful concept. State, Action, Reducer — voilà, you’ve got the new State. Pure and simple. Whenever an action is triggered, a new state is produced immediately thanks to the
synchronous reducer function.
However, saying that this pattern is enough to build every UI application, is a bit too much. The reason is simple:
The front-end world is not synchronous.
As soon as you have to debounce user input, wait for an API response, or cancel an ongoing action, suddenly your application can’t be composed anymore of just the synchronous functions. Synchronous Redux reducer ends up not being enough, because many effects of UI actions are asynchronous, i.e.:
- Actions such as “filtering search results” and “retrieving API response” take time and can’t be dispatched immediately,
- Actions such as “cancel the ongoing request when the new one is done” need to rely on other actions (not the reducer’s state).
Unfortunately, if we go to the core Redux documentation, it seems like we are left with this problem on our own:
It is up to you to try a few options, choose a convention you like, and follow it, whether with, or without the middleware.
— ”Async Actions”, Redux official documentation
Since Redux doesn’t provide us with a solution out of the box, people have built their own libraries to go around it. Let’s take a look at some of the most popular approaches.
1. Async Action Creator
The first approach is to use a middleware that allows us to have special action creators (functions that return an action object which is passed to the action dispatcher) - which instead of returning an action, return a function that dispatches actions asynchronously over time.
Such code is easy to understand, easy to write, and handles most cases of asynchronous actions quite well.
… until a problem like this appears: What if action A needs to be dependant of the action B?
For example, I want to cancel ongoing fetch request when the user changes search filters. Or, I want to fetch them only after 300ms have passed since the last “initialize fetching” action.
Solving this with
redux-promise is troublesome (requires keeping references to the promises/timeouts in a local/global state) and very difficult to do, especially when there’s more than just two actions involved.
2. 4th element of Redux
Another approach is to create a middleware that creates the “4th element” of Redux (next to the action dispatcher, reducer function, and the store).
The “4th element” is something that is constantly listening to the ongoing actions, has a hidden state on its’ own, and finally, as a result of the past actions, dispatches new actions whenever needed.
Depending on the middleware implementation and its desired usage, it might favor using JS generators syntax and some custom utility functions (
redux-ship ), or Observables (
- Saga middleware is created and connected to Redux store,
- Whenever the
FETCH_ARTICLESaction is dispatched (
yield takeLatest()), it gets picked up by the saga middleware, which then asynchronously triggers other actions (i.e. through
redux-saga exports quite a bit of utility functions — besides
takeLatest there is also
cancel , and so on.
- Acting on actions, initiating side effects, and dispatching actions followed by them is extracted to Sagas.
- You can wait for another action, cancel ongoing action, debounce, throttle, etc.
- You can easily unit test it (by testing just a given saga by putting actions into it and seeing what will it yield back)
- Quite easy to learn (~1–2 days).
Although it has over 15000 stars on GitHub, I never liked it too much. Why?
- You need to grasp the JS generators syntax, which besides a few functions of Node.js, is very rarely used in the JS ecosystem. (At least in JS, I’ve never used them anywhere other than
- You need to learn all the
redux-sagautility functions, which in my opinion are quite strange (they don’t remind anything that I’ve seen in other libraries) and are very
- It’s quite difficult (if not impossible) to make it work with TypeScript (at least for now).
The question is, can we get the pros of
redux-saga without learning any custom library-specific APIs? Maybe something, that is even a bit declarative (and easier to read and write)?
What is an Observable? It’s similar to a Promise but it can receive multiple values over time. It’s a powerful thing that is already available in over 15 programming languages. It might actually be a part of JS language one day. If you’re not familiar with them yet, I recommend you read more about them first, before proceeding to the next paragraph.
redux-observable performs a similar job to
redux-saga, but instead of using a
yield generator and providing custom API, it is a simple wrapper on Observables.
redux-saga has its own state machine that controls sagas (and it needs to be controlled with
redux-observable just uses observables as the actual state machines.
You do it by defining an “epic”: an Observable that receives a stream of all the dispatched actions, then filters/maps the actions as you define it, and finally the produced stream of actions is passed to the dispatcher in order to produce new actions.
This will give the same result as the
redux-saga example above, but is written in such a way:
- For all the actions,
- Filter them to
- And when such action happens:
- fetch articles, and once that is done: emit
- Lets you freely manipulate on all the actions, emit new actions asynchronously, and combine them however you like,
- Lets you easily unit test it (each “epic” can be unit tested separately, by just putting the actions into it, and seeing what actions will it emit in return)
- The syntax is declarative — easy to write and read,
- API is completely based on Observable, which will be useful for you many times in the future — not only in Redux-based JS-written front-end applications!
- Supports TypeScript easily.
Nice, isn’t it?
- Redux is an awesome concept, but it gives no solution for creating and handling asynchronous actions.
redux-thunkis enough in the short term, but eventually, you’ll need to switch to something else.
redux-sagaand redux-observable (and other similar libraries) mostly differ only on their outer API and internal implementation.
- I suggest going with the Observables way, as:
a. It is a perfect way to grasp a series of events in time
b. They are getting more and more popular nowadays
c. They are promoted by many other popular libraries: i.e. Angular 2, Apollo (and soon maybe by ECMAScript spec itself)
For illustration and learning purposes, I created a very simple React app that lets you browse, create and delete social events. Find it here:
- “The world is asynchronous”, Werner Vogels, All Things Distributed (15th July 2004)
- “Redux-Saga V.S. Redux-Observable” by Wayne Chiu
- “Why use Redux-Observable over Redux-Saga?” on StackOverflow
- Slides from my “How to handle asynchronous actions in Redux” talk