Redux Saga Polling and Cancellation
The Requirement
We have been given a requirement to allow a user to see the results of an API call as it changes over time. In this use case, the requirement is that our react app calls /my-poll-endpoint
every 3 seconds. Sounds simple enough. We can just use a setInterval
and call it a day, right?
What could go wrong?
Breaking down this requirement, there are a few more cases to consider:
- If the API endpoint becomes invalid, should we keep pinging it?
- If the request doesn’t return before 3 seconds, what happens when multiple
fetch()
calls are queued up? - How do we maintain the ability to cancel polling from outside this component?
While the previous code snippet achieves the initial requirements, these cases could potentially slow down or even halt the webapp if the endpoint misbehaves or the response is mishandled.
Explaining the saga and the “watcher”
To get around this, we can leverage an existing state management library with a middleware to handle data fetching (e.g. redux and redux-saga).
Instead of directly making the API call inside our component code, we call a redux action and move the data fetching logic to inside a saga.
Aside from creating files to specify types and actions, we also create saga.js
to leverage generators and specify steps in a declarative way to handle asynchronous steps and their side effects.
Starting from saga.js
, we see an important default export at the bottom. To ensure that this file is run, we must first call *pollingSaga()
from the app level. If we do not call this generator function, our actions are defined but the side-effect handlers to manage the data passed through from the redux action will not be triggered.
The *pollingSaga()
defines a “watcher” function by using the takeLatest
effect that listens for actions matching a specific type and executes a callback for the most recent event. In this case, the “watcher” function finds a matching type then executes the callback *pollOrCancelEndpoint()
which does two things:
- It defines a “forked polling function” that represents our polling API call with a 3 second delay.
- It passes the reference of that forked function into a separate “cancel watcher” so that at a later point in time, the function can potentially be cancelled.
Because we defined our “cancel watcher” with a reference to the “forked polling function”, we can simply call the action type the “cancel watcher” is looking for without explicitly passing a reference to our “forked polling function”. We already passed the reference in our “cancel watcher” definition! Without needing a reference, we can call this action to cancel polling in the exception block of a try/catch
statement to avoid hammering the server with bad requests.
In the case that our “cancel watcher” is called, it will stop API polling. To resume API polling, we will need to run watchPollEndpoint()
either by remounting the component or explicitly calling the action elsewhere.
Component Changes
With the data fetching logic offloaded to the saga, our component changes slightly by connecting it to redux and simply calling an action. A bonus effect is that since the polling API response is being stored in the reducer and passed to the component via props, we can guarantee a one-directional data flow. This removes the guesswork of potential data mutation inside local component state.
More Control
Moving API polling from the component to the saga allows for separation of concerns between rendering data and API handling into separate files, allowing for easier debugging.
Here at Sight Machine, we use this redux-saga polling pattern to get real time status checks on data streams and pipelines for factory model creation. We’re hiring!