Global Exception Interception and Customized Error Handling in Axios
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
- 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.
- Although the exception handling in sagas is described here, the same principle applies to exception handling within components.