From redux-thunk to sagas

VAMSI DEEPAK AMPOLU
5 min readJul 30, 2016

--

If you have been using redux for a while, you have run into the problem of changing the state of your application using an async operation such as a timer or a HTTP request.

If we were not bound by the contract provided by redux, we would litter our application with state and just update the state after a Promise resolves or mutate application within a function in a setTimeout. But we know better, we have been burned by mutable state, inconsistencies and bugs.

To keep our discussion general, let’s assume that we have already built reducers to reflect the application state. This way, we can discuss around actions and middleware.

To perform an async request to the server using redux we define multiple actions:

  1. MAKE_ASYNC_REQUEST

2. MAKE_ASYNC_REQUEST_PROGRESS

3. MAKE_ASYNC_REQUEST_SUCCESS

4. MAKE_ASYNC_REQUEST_FAILURE

When the async operation needs to be started, either on user action or at the beginning of the application, the first action is dispatched. The second action is optional, it is dispatched to indicate the progress of a request(say a loading bar). If you have no such requirement, it would be best to set a flag on the application state to display a loading indicator.

Only one of the third and fourth actions is dispatched. They indicate the failure or the success of the request and ensure that the application state and the UI reflect that.

The popular abstraction is to dispatch a function instead of an action. This function has access to dispatch as its first argument and can be used to dispatch multiple actions as required.

Using thunks to perform an async operation

We use a middleware called redux-thunk to handle functions dispatched using redux. One thing to be noted here is that we can dispatch anything using redux as long as we have the appropriate middleware to convert it to flux standard actions.

We could dispatch Promises, Observables or even Generators if we have the appropriate middleware to handle them. Using thunks or promises to describe async operations can be difficult to reason about.

Also, we are restricted to reasoning about each effect on its own. In some cases, our action might need inputs from an offline store, a web API and a piece of existing application state.

To put it simply, thunks are a good starting point but they do not scale. A more preferable approach would be to use redux-saga middleware to orchestrate our asynchronous workflows.

A saga is a failure management pattern which allows for a process to be divided into several smaller processes. When one of these sub processes succeeds or fails, it updates application state with the information.

To understand sagas better, you might want to take a look at this:

redux-saga also draws inspiration from Communicating Sequential Processes(CSP), a concurrency pattern that relies on multiple processes communicating with one another. In our case, at any given moment, there are only two communicating processes. All processes communicate directly with the middleware.

A process is represented by a generator. Generators are a special kind of function which can pause itself and be resumed from the outside. A generator is essentially a puppet and an iterator is controlling it. For strings, the iterator it’s next and throw methods.

A generator is naive, it asks the iterator questions and waits until the iterator replies, providing it with values that the generator uses to evaluate expressions until it reaches the next question.

In the example below, the generator tells the iterator `3` and the iterator replies by throwing an error into the generator. Also notice how calling the generator does not execute its body but create the iterator.

This example by Kyle Simpson demonstrates a simple generator

The author Kyle Simpson wrote a series of blog posts about Generators here. If you want to know more about generators, read:

And yes, before you ask, I stole the example above from there. To communicate with the middleware, the saga sends an object telling the middleware what it wants to do. This is known as an effect and draws parallels to actions in the redux.

redux-saga has several kinds of effects, some of them are generic, some are specific to redux and others enable thread like concurrency. Here are some of the popular action creators

Let us make the same async request using sagas:

Look Ma, no callbacks

We obtain the middleware by invoking the createSagaMiddleware function which we import from redux-saga. In addition to registering itself as a middleware when the store is created, it can be used to run your application’s main saga.

We can use the mainSaga to start all the other sagas we need. These sagas are often known as worker sagas. Here we yield an effect to the sagaMiddleware, telling it to call the asyncRequestSaga with the value info as an argument.

If we had multiple sagas within the mainSaga, we would use fork to start each saga instead of call, because fork is designed to invoke a saga and move on to the next step while call blocks on a saga until it completes.

In the asyncRequestSaga, we recieve some data from the action and use call to tell the saga middleware to call the async API. To add some complexity, let us pretend that we need some piece of application state. We can tell the saga middleware to run a function to retrieve the state we need.

We then use `put` to let the middleware know that it should dispatch an action. And error handling is easy, just use try-catch.

If you liked the approach here, you should check out some of the libraries below. Be warned, this list is based on my experience as a Javascript window-shopper only:

If you are brave and willing to explore other languages, you can check out:

These have a little more overhead and these two resources might not have a huge impact on you but check them out anyways.

--

--