Keep Trying: Redux Saga Style

Photo by Quino Al on Unsplash

With redux-saga we are able to create what are effectively imperative threads in our front-end applications that control user flow and generally handle our side-effects. Since side-effects are often a point of failure in front-end applications it is not uncommon to see the use of try/catch blocks like so:

function* fetchProducts() {
try {
const products = yield call(Api.fetch, '/products')
yield put({ type: 'PRODUCTS_RECEIVED', products })
}
catch(error) {
yield put({ type: 'PRODUCTS_REQUEST_FAILED', error })
}
}

Although the above is a great pattern, and allows us to build an intelligent UI that can display to the user that something went wrong, the fact remains that the user is sitting on an empty products page.

What if we wanted to keep trying to acquire products? Essentially, what if we wanted to automatically restart this particular part of our codebase in the event that something has gone wrong?

Let us also assume that this is a pattern we will want to reuse. Wrapping potential trouble areas in our codebase to prevent the codebase from becoming unresponsive is a handy thing.

Enter our autoRestart higher-order function:

// Pass in our generator we want to restart, and some function
// to handle potential errors thrown within said generator.
const autoRestart = (generator, handleError) => {
return function * autoRestarting (...args) {
while (true) {
try {
yield call(generator, ...args)
break
} catch (e) {
yield handleError(e)
}
}
}
}

Using this with our fetchProducts saga above is easy enough, with one modification to have our fetchProducts throw an error when failing:

const fetchProducts = autoRestart(
function * fetchProducts () {
try {
const products = yield call(Api.fetch, '/products')
yield put({ type: 'PRODUCTS_RECEIVED', products })
}
catch(error) {
throw new Error(error)
}
},
function * handleError (e) {
yield put({ type: 'PRODUCTS_REQUEST_FAILED', e })
}
)

In essence this will continue to attempt to reconnect to the API to get our products until it is successful. The beauty of redux-saga is definitely in the ability to control user flow, both the “happy path” that we hope the user experiences, and in the event of a failure of any part of the system.

Although outside the scope of this article, we could have our Api.fetch return a server error that is handled as well, or create scenarios where we want to break out of our restarting loop depending on the error type.

A simple running example of this pattern can be found at bfillmer/saga-restart.