52

I have the standard then/catch axios code all over my app, a simple one goes likes this..

axios.get('/').then( r => {} ).catch( e => {} )

The problem I have with the above is that I have to duplicate the catch() block to handle any potential errors that my be invoked in my app and my questions is, if there is anything I can do to catch the errors globally from on entry point as opposed to using catch everywhere.

I am looking for solutions from either axios side or vue, since my app is built with vue

hidar
  • 5,449
  • 15
  • 46
  • 70

3 Answers3

95

You should use an interceptor.

First, create an axios instance using the create method. This is what you would need to use throughout your app instead of referencing axios directly. It would look something like this:

let api = axios.create({
  baseURL: 'https://example.com/api/',
  timeout: 1000,
  headers: {'X-Custom-Header': 'foobar'}
});

Then attach an interceptor to your axios instance to be called after the response to each of the requests for that instance:

api.interceptors.response.use((response) => response, (error) => {
  // whatever you want to do with the error
  throw error;
});
Stephen Ostermiller
  • 23,933
  • 14
  • 88
  • 109
thanksd
  • 54,176
  • 22
  • 157
  • 150
  • Are you sure the syntax is right? My pages are just loading infinitely with a blank page ... it seems the page does not get loaded ... should I return something from the second code? – hidar Feb 26 '18 at 15:27
  • Make sure you are returning the response in the first handler: `(response) => response`. You don't need to return anything in the error handler – thanksd Feb 26 '18 at 15:28
  • Not working .. my code is cluttered with try/catch errors ... so with your answer I should remove all `.catch()` part, will that do the job? – hidar Feb 26 '18 at 15:31
  • I wouldn't think so, but why not try it and see?... hard to know what's causing the errors without seeing your code – thanksd Feb 26 '18 at 15:39
  • No, I mean in general .. if i use the interceptor to catch only error, and let the response pass ... do I remove all catch() blocks from my app as long as I use the interceptor to catch them .. – hidar Feb 26 '18 at 15:41
  • 1
    You can. Unless you want to handle specific errors after specific calls. Then you'd need to throw the error in the interceptor handler and then you could catch it in the handler for a specific call. In fact, I think, to be safe, you should probably throw the error (see my edit). – thanksd Feb 26 '18 at 15:43
  • This intercept is called before catch. I assume you changed the baseURL also check the network tab to see what’s gone wrong (network tab in chrome dev tools) – Sigex Jul 25 '19 at 23:43
  • You can use `undefined` instead of `(response) => response`. Anyway, how do I create the interceptor once and reuse it wherever I need to attach it to an `AxiosInstance`? I'm guessing the answer is to define a function with one param `error` and just pass it to all the `use` functions (as the second argument). Example: `use(undefined, handleAxiosError);` with `handleAxiosError = (error) => { ... do your thing ...; throw error; }`. But is there another recommended method? – ADTC Oct 31 '22 at 06:15
33

While just handling errors globally inside the interceptor works in some case, there are times when you'd want more control as to whether the error should be handled globally.

I personally compose errors globally and call the handlers locally. With this approach, i can decide to not handle the error globally in some cases. I can also decide to invoke the global handler only when certain conditions are met.

Below is a simple implementation of a globally composed error handler. To better understand this technique, you may want to check this article (A short story on ajax error handlers).

import axios from 'axios';
import {notifier} from './util';

// errorComposer will compose a handleGlobally function
const errorComposer = (error) => {
    return () => {
        const statusCode = error.response ? error.response.status : null;
        if (statusCode === 404) {
            notifier.error('The requested resource does not exist or has been deleted')
        }

        if (statusCode === 401) {
            notifier.error('Please login to access this resource')
        }
    }
}

axios.interceptors.response.use(undefined, function (error) {
    error.handleGlobally = errorComposer(error);

    return Promise.reject(error);
})


// Fetch some missing information
axios.get('/api/articles/not-found').then(resp => {
    // Do something with article information
}).catch(error => {
    const statusCode = error.response ? error.response.status : null;
    // We will handle locally
    // When it's a 404 error, else handle globally
     if (statusCode === 404) {
        // Do some specific error handling logic for this request
        // For example: show the user a paywall to upgrade their subscription in order to view achieves
    } else {
        error.handleGlobally && error.handleGlobally();
    }
})

// Fetch some missing information
axios.get('/api/users/not-found').then(resp => {
    // Do something with user information
}).catch(error => {
    // We want to handle globally
    error.handleGlobally && error.handleGlobally();
})
Emeke Ajeh
  • 933
  • 1
  • 10
  • 16
  • 7
    This is what really annoys me about axios - the error-handling API is an absolute mess. I had the same issues as the OP and even with your suggestion I'm not happy with it. What it SHOULD allow me to do, is be able to automatically handle certain things but still allow me to override, which it can't. – Oddman Dec 24 '20 at 23:18
  • @Oddman I think that might be due to the fact that it uses promises and when promises throws error the only way to handle it is via the via the catch method. If it was possible to check if the promise has a catch method chained or not then that would have added more flexibility. – Emeke Ajeh Jan 06 '21 at 16:19
  • They could manage it in their own way using a chain of command pattern, and make it look like a promise. That would have been far, far better. It honestly feels like they found a hammer and looked for a nail. – Oddman Jan 13 '21 at 07:03
  • I am using Typescript and learning react atm. Can you tell me what is the type of error in the errorComposer? – Benjamin Martin Dec 28 '21 at 17:24
  • 1
    @BenjaminMartin this will be AxiosError from: import { AxiosError, AxiosInstance, AxiosRequestConfig } from 'axios'; – Emeke Ajeh Dec 29 '21 at 18:32
-2

You could use Axios Multi API. It solves this issue by having a simple onError callback that you can set when you create your API (disclaimer: I'm the author of the package). I in fact created it because I was tired of reinventing the wheel in many projects.

import { createApiFetcher } from 'axios-multi-api';

const api = createApiFetcher({
    apiUrl: 'https://example.com/api/',
    apiEndpoints: {
      getUserDetails: {
          method: 'get',
          url: '/user-details/get',
      },
    },
    onError(error) {
      console.log('Request has failed', error);
    }
});

const data = api.getUserDetails({ userId: 1 });
Matt
  • 225
  • 3
  • 3
  • 8
    Installing _yet another_ npm package is not the correct solution here. The correct solution is to use interceptors. – robertmain Jul 06 '21 at 05:06