Handling Exceptions in Redux-Saga

· 3 min read · 558 Words · -Views -Comments

redux-saga manages application side effects (async data fetching, browser storage, etc.). It aims to make side effects easier to manage, more efficient to run, simpler to test, and more resilient when things go wrong.

Inevitably, effects can throw. For instance, an API call may fail. How should we handle these errors? That’s the topic here.

What If We Ignore Errors?

Consider an effect that calls the backend three times in sequence:

If the second getBooks request fails, will “step 2” print?

It doesn’t. Once the request throws, the effect aborts immediately.

Handling a Single Request

Wrap the effect in try…catch:

Catching the error swallows it, allowing subsequent steps to run. The console still prints step1, step2, and step3.

What Counts as an Exception?

HTTP 200, 400, 404, 500…which ones trigger catch?

The demo uses Axios. The key is validateStatus:

validateStatus: function validateStatus(status) {
  return status >= 200 && status < 300;
}
module.exports = function settle(resolve, reject, response) {
  const validateStatus = response.config.validateStatus;
  if (!validateStatus || validateStatus(response.status)) {
    resolve(response);
  } else {
    reject(createError(
      'Request failed with status code ' + response.status,
      response.config,
      null,
      response.request,
      response
    ));
  }
}

Responses outside 200–299 fall into the catch. 300-series responses trigger browser redirects before Axios resolves.

Axios source: defaults.js

Preventing a Saga Tree Crash

If a single effect throws, the watcher stops, potentially taking down the entire saga tree. To keep the app safe, wrap each effect.

import { call } from 'redux-saga/effects';

export function safe(sagaFn) {
  return function* (action) {
    try {
      return yield call(sagaFn, action);
    } catch (e) {
      console.error('[react-demo | Saga Unhandled Exception] This error should be fixed or guarded in the saga.');
      console.error(e);
    }
  };
}

Apply the Wrapper

When an Effect Throws, the Watcher Stops

Suppose the component dispatches the same action twice:

Only one log entry appears:

Build a Reusable Wrapper

To avoid repeating try…catch, wrap the effect before registering it:

import { call } from 'redux-saga/effects';

export function safe(sagaFn) {
  return function* (action) {
    try {
      return yield call(sagaFn, action);
    } catch (e) {
      console.error('[react-demo | Saga Unhandled Exception] This error should be fixed or guarded in the saga.');
      console.error(e);
    }
  };
}

Then wire it up:

function* mySaga() {
  yield takeEvery('USER_FETCH', fetchUserEffects);
  yield takeEvery('TEST_SAGA', safe(testSagaEffects));
  yield takeEvery('TEST_SAGA', testSagaEffects2);
}

Result

Even if the first effect fails, the second still runs.

Handling Errors Globally?

Wrapping every effect works but feels repetitive. Can we automate it?

Yes—use effectMiddlewares:

const effectMiddleware = (next) => (effect) => {
  if (effect.type === 'FORK') {
    effect.payload.args[1] = safe(effect.payload.args[1]);
  }
  return next(effect);
};

export const sagaMiddleware = createSagaMiddleware({
  effectMiddlewares: [effectMiddleware]
});

Now you don’t need to decorate each effect manually—it’s handled once.

Heads-Up

effectMiddlewares arrived in redux-saga 1.0.0. Upgrade if you’re on an older version.

Release notes: v1.0.0

Why FORK?

  • Saga effects include types such as TAKE, PUT, ALL, FORK, etc.
  • takeEvery and takeLatest produce FORK effects under the hood.

Error Messages Feel Sparse?

Yes—the logs only show the action name, not the exact code location inside the effect.

Final Thoughts

  1. Wrapping effects keeps watchers alive, but ask why the error happens. Hiding it doesn’t make the app safer. Often it’s better to fix the root cause.
  2. Libraries like saga are powerful, but every abstraction comes with trade-offs. Learn the internals and build custom helpers when needed.

References

Authors
Developer, digital product enthusiast, tinkerer, sharer, open source lover