Polling with Redux

A brief guide on how to poll with Redux using either redux-saga or redux-observable.

Share

For the past few months we’ve been working on the LiveBlog WordPress plugin by Automattic to update the UI to use React + Redux. The LiveBlog plugin uses polling to get the live updates, so we’re going to look into how to implement polling with Redux.

There are two libraries that we looked at to accomplish this; redux-saga and redux-observable. As a very high level overview of them both:

Redux-saga uses ES6 generator functions to make asynchronous flow easy to read. They look similar to the async / await syntax.

Redux-observable is RxJS-based, which is a library for reactive programming using Observables.

Let’s get polling!

To see that polling works, we’re going to use a random joke API, so every time a polling request comes back, we’ll see a new joke appear. What we will accomplish is a poll that runs every 4 seconds, and will wait for the request resolve before starting the poll again. This means if the request more than seconds, the poll will wait and resume once the request has come back.

Redux-Saga

Let’s start with the redux-saga implementation:

/**
 * Saga worker.
 */
function* pollSagaWorker(action) {
  while (true) {
    try {
      const { data } = yield call(() => axios({ url: ENDPOINT }));
      yield put(getDataSuccessAction(data));
      yield call(delay, 4000);
    } catch (err) {
      yield put(getDataFailureAction(err));
    }
  }
}

/**
 * Saga watcher.
 */
function* pollSagaWatcher() {
  while (true) {
    yield take(POLL_START);
    yield race([
      call(pollSagaWorker),
      take(POLL_STOP)
    ]);
  }
}

On first glance it looks like there is a lot to digest. Let’s break it down.

The worker

The worker generator function is the main function that performs the API request and delay. As we’re polling, we need to wrap it in a while loop. This to make it a continuous task and keeps poll running. Without the while loop, it would only run once and finish.

Inside we use a try/catch for handling errors in the request. Let’s take a look at inside the try block:

const { data } = yield call(() => axios({ url: ENDPOINT }));

yield in a generator pauses the function, and in this scenario waits for the redux-saga call effect creator to be resolved before moving on to the next. The call effect creator instructs the middleware to call the function passed in—in this case—axios. We use axios as it’s more feature rich than the Fetch API and works across all browsers.

yield put(getDataSuccessAction(data));
yield call(delay, 4000);

Next, we use the put effect creator to dispatch an action to the Redux Store, in this case, a success action with a the data payload from the response. Once this is complete, it moves on to calling the redux-saga delay utility function, which returns a promise that resolves after a specified amount of time. This essentially pauses the worker for 4 seconds, creating the poll.

The watcher

To be able to run the poll worker, we need a watcher generator. The watcher will handle the “watching” for a specific action (or actions) and call the worker.

while (true) {
  ...
}

Again, we start with a while loop to ensure the task keeps running. Without it, we would only be able to start the poll watcher once, so if the poll gets cancelled, it won’t be able to start again.

yield take(POLL_START);

We use the take effect creator, which instructs the middleware to wait for an action on the store. Here we wait for the POLL_START action.

yield race([
  call(pollSagaWorker),
  take(POLL_STOP)
]);

Once the POLL_START action has been received, we use the race effect combinator to either call the pollSagaWorker function, or take the POLL_STOP action. race will start a race between multiple effects, and will automatically cancel the losing effect(s). This means if the action POLL_STOP is received, it will cancel the pollSagaWorker function.

Here is a live demo:

See the Pen Redux + redux-saga polling by markgoodyear (@markgoodyear) on CodePen.

Redux-Observable

Redux-observable uses Epics to handle Redux side effects. An Epic, as quoted from the documentation, is:

a function which takes a stream of actions and returns a stream of actions. Actions in, actions out.

In terms of polling, the poll start action will be recieved by the Epic, we then perform a request and then dispatch an action with the data from the request.

With that in mind, let’s take a look at the redux-observable implementation:

const pollEpic = action$ =>
  action$.ofType(POLL_START)
    .switchMap(() =>
      Observable.timer(0, 4000)
        .takeUntil(action$.ofType(POLL_STOP))
        .exhaustMap(() =>
          Observable.ajax({ url: ENDPOINT, crossDomain: true })
            .map(res => getDataSuccessAction(res.response))
            .catch(error => Observable.of(getDataFailureAction(error)))
        )
    );

This is composed of RxJS Observables and operators. The first thing is setting up the Epic to “listen” for the POLL_START action. This uses the redux-observable ofType operator, which is an RxJS filter operator behind the scenes. The rest of the operators we use are from RxJS directly; switchMap, exhaustMap, takeUntil, map and catch, along with three Observables, timer, of and ajax.

A breakdown of what is happening:

  • On POLL_START, switch to the inner timer Observable using switchMap.
  • Run the timer Observable until a POLL_STOP action is receieved via the takeUntil operator.
  • Switch to the inner ajax Observable using exhaustMap. We use the RxJS ajax Observable instead of axios, or another promise based solution, as it’s alread an Observable, and provides automatic request cancelation if the Obseravble is cancelled. by using exhaustMap, the outer Observable (timer) will wait on the ajax Observable to complete before resuming.
  • We then use map will then dispatch the success action to the Redux Store, with the request response.

Check out the live example here:

See the Pen Redux + redux-observable polling by markgoodyear (@markgoodyear) on CodePen.


This should give you a firm grasp on how to poll with Redux using either redux-saga or redux-observable. For the LiveBlog we decided to use redux-observable, as being RxJS based, it allows us to utilise RxJS throughout the rest of the plugin. Also, because RxJS isn’t tied into Redux, both code and concepts are portable outside the Redux world. Happy polling!

If you have any comments or questions you can find me on Twitter.


Interested in learning more about enterprise WordPress?
Get in touch with the team today