Global Exception Interception and Customized Error Handling in Axios

· 3 min read

In the current project, some common exceptions like 500, 400 need to be handled uniformly across the application, while for some request exceptions, like 400, we want specific functional blocks to handle them in a customized way. This creates a conflict, so I started looking for a solution.

Partial Tech Stack

Background

We use Axios interceptors globally for implementation. The specific request code is generally handled in sagas, so customized exception handling also occurs there.

  • Redux-Saga
  • Axios

Current Solution

Axios

import axios from 'axios';

const onResponseSuccess = response => {
  return response;
};
const onResponseError = err => {
  const status = err.status || err.response.status;
  if (status === 403 || status === 401) {
    alert('no auth');
  }

  if (status >= 500 || status === 400) {
    console.error('[axios-global]invalid request');
  }
  return Window.Promise.reject(err);
};

axios.interceptors.response.use(onResponseSuccess, onResponseError);

Saga-effects

function* testExceptionEffects() {
  try {
    yield call(getBadRequest);
  } catch (e) {
    console.error('saga exception');
  }
}

Existing Issues

If getBadRequest returns a 400 status, both places will handle the exception - both the Axios interceptor and the saga. And the execution order will be: axios global interceptor => saga.

Exception Messages

Expectations

If we completely stop handling 400 errors globally, then each place would need to handle them individually, which is not my goal. I want global handling to still process common exceptions like 400, but also give certain requests the ability to handle exceptions in a customized way.

Solution

The execution order from Axios interceptors to saga exception catching cannot be modified, so what we can do is add a customization option in Axios to distinguish between customized and common exception handling strategies.

Axios

const onResponseError = err => {
  const config = err.config; // axios config
  const status = err.status || err.response.status;
  if (status === 403 || status === 401) {
    alert('no auth');
  }

  if (status >= 500) {
    console.error('[axios-global]server error');
  }
  if (status === 400 && (config.errorHandle === undefined || config.errorHandle === false)) {
    console.error('[axios-global]bad request');
  }
  return Window.Promise.reject(err);
};

API

export const getBadRequest = () => axios.get('http://mock-api.com/wz2wL5gL.mock/test/bad-request', {
  errorHandle: true
});

Result

If we set errorHandle=true, the interceptor will not process the 400 error and will directly throw it, allowing the saga to perform customized handling immediately afterward. This way, the conflict handling shown above won’t occur.

If we still want certain requests to go through the global 400 exception handling, we can simply not add this configuration, and also not perform customized exception handling.

Conclusion

  1. The solution itself is relatively simple, but it addresses the need for personalized exception handling in specific business scenarios. It is more flexible compared to directly handling everything in a one-size-fits-all manner.
  2. Although the exception handling in sagas is described here, the same principle applies to exception handling within components.