Handling Asynchronous Actions in Redux

An in-depth look at the most popular approaches

Jack Tomaszewski
Jul 9 · 6 min read
Every Redux app at a glance?

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).
Actions marked as red are asynchronous

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


Solutions

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.

Async Action Creator (i.e. redux-thunk)

Libraries that follow this practice: redux-thunk, redux-promise.

Example: redux-thunk

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-thunk or 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.

The 4th element of Redux — redux-saga state machine

Depending on the middleware implementation and its desired usage, it might favor using JS generators syntax and some custom utility functions ( redux-saga, redux-ship ), or Observables ( redux-observable, redux-cycles ).

Example: redux-saga

In short:

  • Saga middleware is created and connected to Redux store,
  • Whenever the FETCH_ARTICLES action is dispatched (yield takeLatest()), it gets picked up by the saga middleware, which then asynchronously triggers other actions (i.e. through yield put()).

redux-saga exports quite a bit of utility functions — besides takeLatest there is also takeEvery, put, join, cancel , and so on.

Pros:

  • 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 thanredux-saga.)
  • You need to learn all the redux-saga utility functions, which in my opinion are quite strange (they don’t remind anything that I’ve seen in other libraries) and are very redux-saga-specific.
  • 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)?

Example: redux-observable

Meet redux-observable (or NgRx) and Observables.

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.

While redux-saga has its own state machine that controls sagas (and it needs to be controlled with redux-saga functions), 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.

The 4th element of Redux — redux-observable

Example A:

This will give the same result as the redux-saga example above, but is written in such a way:

  1. For all the actions,
  2. Filter them to FETCH_ARTICLES action,
  3. And when such action happens:
    - emit FETCH_ARTICLES_START action
    - fetch articles, and once that is done: emit FETCH_ARTICLES_SUCCESS / FETCH_ARTICLES_FAILURE action.

Pros:

  • 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.

Example B:

Example C:

Nice, isn’t it?


Summary

  1. Redux is an awesome concept, but it gives no solution for creating and handling asynchronous actions.
  2. redux-thunk is enough in the short term, but eventually, you’ll need to switch to something else.
  3. redux-saga and redux-observable (and other similar libraries) mostly differ only on their outer API and internal implementation.
  4. 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)

Demo App

For illustration and learning purposes, I created a very simple React app that lets you browse, create and delete social events. Find it here:


Better Programming

Advice for programmers.

Jack Tomaszewski

Written by

Techno-hippie and a full-stack web developer (www.jtom.me) for over 15 years. Currently looking for his next business venture.

Better Programming

Advice for programmers.