323

I'm trying to understand javascript promises better with Axios. What I pretend is to handle all errors in Request.js and only call the request function from anywhere without having to use catch().

In this example, the response to the request will be 400 with an error message in JSON.

This is the error I'm getting:

Uncaught (in promise) Error: Request failed with status code 400

The only solution I find is to add .catch(() => {}) in Somewhere.js but I'm trying to avoid having to do that. Is it possible?

Here's the code:

Request.js

export function request(method, uri, body, headers) {
  let config = {
    method: method.toLowerCase(),
    url: uri,
    baseURL: API_URL,
    headers: { 'Authorization': 'Bearer ' + getToken() },
    validateStatus: function (status) {
      return status >= 200 && status < 400
    }
  }

  ...

  return axios(config).then(
    function (response) {
      return response.data
    }
  ).catch(
    function (error) {
      console.log('Show error notification!')
      return Promise.reject(error)
    }
  )
}

Somewhere.js

export default class Somewhere extends React.Component {

  ...

  callSomeRequest() {
    request('DELETE', '/some/request').then(
      () => {
        console.log('Request successful!')
      }
    )
  }

  ...

}
mignz
  • 3,648
  • 2
  • 20
  • 21
  • Do you want to break the promise chain? – Niyoko Apr 22 '18 at 15:57
  • Not sure. Does that stop me from having to use catch when I call the request function? – mignz Apr 22 '18 at 16:03
  • Is an unsuccessful status code logically an exceptional state in your application? How would you expect calling code to react to it? – Benjamin Gruenbaum Apr 22 '18 at 19:07
  • 1
    If you send errors down the success path, you will, in all probability, need to test for them in order to branch at some higher level. I'd say allow success to be success and errors to be errors, and .catch() accordingly. – Roamer-1888 Apr 23 '18 at 01:04

14 Answers14

435

If you want to handle all basic errors in your request module, without needing to use catch on each call, then the Axios approach is to use an interceptor on the responses:

axios.interceptors.response.use(function (response) {
    // Optional: Do something with response data
    return response;
  }, function (error) {
    // Do whatever you want with the response error here:

    // But, be SURE to return the rejected promise, so the caller still has 
    // the option of additional specialized handling at the call-site:
    return Promise.reject(error);
  });

If you return the error from your axios interceptor, then you can still also use the conventional approach via a catch() block, like below:

axios.get('/api/xyz/abcd')
  .catch(function (error) {
    if (error.response) {
      // The request was made and the server responded with a status code
      // that falls out of the range of 2xx
      console.log(error.response.data);
      console.log(error.response.status);
      console.log(error.response.headers);
    } else if (error.request) {
      // The request was made but no response was received
      // `error.request` is an instance of XMLHttpRequest in the browser 
      // and an instance of http.ClientRequest in node.js
      console.log(error.request);
    } else {
      // Something happened in setting up the request that triggered an Error
      console.log('Error', error.message);
    }
   
  });
XML
  • 19,206
  • 9
  • 64
  • 65
Plabon Dutta
  • 6,819
  • 3
  • 29
  • 33
  • 1
    Why do you just use simple returns in your `then` parts and `Promise.reject` in your `catch` parts? Doesn't seem really consistent to me. – winklerrr Sep 22 '20 at 16:04
  • 1
    I used the snippet from official `axios` documentation on github. https://github.com/axios/axios#interceptors – Plabon Dutta Sep 22 '20 at 16:54
  • 4
    Anyway, I think you're referring to the `interceptor` part, but there is no `then` there. Requests or responses are being intercepted before being handled and thus, we do not want to `Promise.resolve()` yet. However, if an error is encountered, we can `Promise.reject()` if we want to. Or, we can return something and later when the request or response will be handled, we can use `Promise.reject()`. Same thing. – Plabon Dutta Sep 22 '20 at 17:03
  • Ah yes, that makes sense! So in the error case we already know, that the request doesn't need to be handled further and therefore the promise can already be rejected. – winklerrr Sep 23 '20 at 06:57
  • 1
    Try `error.response` . Hope all are looking for this. It will give proper error details returned by server. – Anuj Raghuvanshi Jan 26 '21 at 16:50
  • `.catch(error) { if (error.response) ... }` axios always returns `error.response` and `error.request`, so I'm not sure if this worked with any of you correctly, but it did not work for me, even though this approach is mentioned in the docs – Normal May 11 '22 at 13:37
  • 1
    it's `catch(error) { ... }` and not `catch(function(error) { ... } )` where did you bring that from? – Normal May 11 '22 at 13:39
  • I brought it from axios documentation, mate! – Plabon Dutta Mar 20 '23 at 19:59
132

If you want to gain access to the whole the error body, do it as shown below:

 async function login(reqBody) {
  try {
    let res = await Axios({
      method: 'post',
      url: 'https://myApi.com/path/to/endpoint',
      data: reqBody
    });

    let data = res.data;
    return data;
  } catch (error) {
    console.log(error.response); // this is the main part. Use the response property from the error object

    return error.response;
  }

}
elonaire
  • 1,846
  • 1
  • 11
  • 17
  • if the error is not caught or it hangs, put another try-catch under the error block – Durian Jaykin Oct 14 '22 at 04:29
  • What do you mean by "gain access to the whole error body"? The answers provided above/previously by @plabondutta provide access to the whole error... – XML Jul 31 '23 at 14:18
98

You can go like this: error.response.data
In my case, I got error property from backend. So, I used error.response.data.error

My code:

axios
  .get(`${API_BASE_URL}/students`)
  .then(response => {
     return response.data
  })
  .then(data => {
     console.log(data)
  })
  .catch(error => {
     console.log(error.response.data.error)
  })
Md Abdul Halim Rafi
  • 1,810
  • 1
  • 17
  • 25
36

If you wan't to use async await try

export const post = async ( link,data ) => {
const option = {
    method: 'post',
    url: `${URL}${link}`,
    validateStatus: function (status) {
        return status >= 200 && status < 300; // default
      },
    data
};

try {
    const response = await axios(option);
} catch (error) {
    const { response } = error;
    const { request, ...errorObject } = response; // take everything but 'request'
    console.log(errorObject);
}
Ben T
  • 4,656
  • 3
  • 22
  • 22
user4920718
  • 1,196
  • 1
  • 10
  • 13
7

I tried using the try{}catch{} method but it did not work for me. However, when I switched to using .then(...).catch(...), the AxiosError is caught correctly that I can play around with. When I try the former when putting a breakpoint, it does not allow me to see the AxiosError and instead, says to me that the caught error is undefined, which is also what eventually gets displayed in the UI.

Not sure why this happens I find it very trivial. Either way due to this, I suggest using the conventional .then(...).catch(...) method mentioned above to avoid throwing undefined errors to the user.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Farhan Kassam
  • 121
  • 1
  • 2
  • 7
    `try..catch` only works with `async/await`. You can either handle it using `.catch()` (catch rejected promise) or `try { await axios... } catch (err) {...}` (catch exception resulting from rejected promise) – BankBuilder Jul 01 '21 at 17:41
6

For reusability:

create a file errorHandler.js:

export const errorHandler = (error) => {
  const { request, response } = error;
  if (response) {
    const { message } = response.data;
    const status = response.status;
    return {
      message,
      status,
    };
  } else if (request) {
    //request sent but no response received
    return {
      message: "server time out",
      status: 503,
    };
  } else {
    // Something happened in setting up the request that triggered an Error
    return { message: "opps! something went wrong while setting up request" };
  }
};

Then, whenever you catch error for axios:

Just import error handler from errorHandler.js and use like this.
  try {
    //your API calls 
  } catch (error) {
    const { message: errorMessage } = errorHandlerForAction(error);
     //grab message
  }
Sundar Gautam
  • 445
  • 1
  • 6
  • 10
4

If I understand correctly you want then of the request function to be called only if request is successful, and you want to ignore errors. To do that you can create a new promise resolve it when axios request is successful and never reject it in case of failure.

Updated code would look something like this:

export function request(method, uri, body, headers) {
  let config = {
    method: method.toLowerCase(),
    url: uri,
    baseURL: API_URL,
    headers: { 'Authorization': 'Bearer ' + getToken() },
    validateStatus: function (status) {
      return status >= 200 && status < 400
    }
  }


  return new Promise(function(resolve, reject) {
    axios(config).then(
      function (response) {
        resolve(response.data)
      }
    ).catch(
      function (error) {
        console.log('Show error notification!')
      }
    )
  });

}
Damir Miladinov
  • 1,224
  • 1
  • 12
  • 15
  • Won't it cause a memory leak? UX would also be "strange". Endless spinner instead of clear error message in right place on the page. Guess that's not what makes users happy. My vote for adding honest error handler in each place where you do a request!) – 12kb Oct 01 '21 at 20:32
4
axios
  .get(`${API_BASE_URL}/students`)
  .then(res => {
     return res.data
  })
  .then((data)=> {
     console.log(data)
  })
  .catch(error => {
     console.log(error)
  })

try this way, it's working fine

3

https://stackabuse.com/handling-errors-with-axios/

    let res = await axios.get('/my-api-route');

    // Work with the response...
} catch (err) {
    if (err.response) {
        // The client was given an error response (5xx, 4xx)
    } else if (err.request) {
        // The client never received a response, and the request was never left
    } else {
        // Anything else
    }
}
try {
    let res = await axios.get('/my-api-route');

    // Work with the response...
} catch (err) {
    if (err.response) {
        // The client was given an error response (5xx, 4xx)
    } else if (err.request) {
        // The client never received a response, and the request was never left
        console.log(err.request);
    } else {
        // Anything else
    }
}
2

call the request function from anywhere without having to use catch().

First, while handling most errors in one place is a good Idea, it's not that easy with requests. Some errors (e.g. 400 validation errors like: "username taken" or "invalid email") should be passed on.

So we now use a Promise based function:

const baseRequest = async (method: string, url: string, data: ?{}) =>
  new Promise<{ data: any }>((resolve, reject) => {
    const requestConfig: any = {
      method,
      data,
      timeout: 10000,
      url,
      headers: {},
    };

    try {
      const response = await axios(requestConfig);
      // Request Succeeded!
      resolve(response);
    } catch (error) {
      // Request Failed!

      if (error.response) {
        // Request made and server responded
        reject(response);
      } else if (error.request) {
        // The request was made but no response was received
        reject(response);
      } else {
        // Something happened in setting up the request that triggered an Error
        reject(response);
      }
    }
  };

you can then use the request like

try {
  response = await baseRequest('GET', 'https://myApi.com/path/to/endpoint')
} catch (error) {
  // either handle errors or don't
}
David Schumann
  • 13,380
  • 9
  • 75
  • 96
  • Sorry to nitpick, but two things: if you really want to use `async` move it down in front of your promise resolve/reject function. Or ideally, don't even bother using the promise (since you're wrapping the baseRequest in an async decorator) and just continue with your try/catch and error type branching and just use `return` instead of `resolve`. Secondly, I like that test for absent server responses! But when axios times out, will it throw an exception and be treated as if the server didn't return a response? or are these scenarios the same thing? – airtonix May 13 '20 at 08:05
  • Thanks for the suggestion airtonix. This function was pretty old and I am always happy to improve code. The mix of async/await and Promises in this function is not ideal. Can you edit my comment to reflect those changes? RE your question AFAIK axios treats both in the `catch` portion of the code. I would manually set the timeout to be very low to test my error handling of timeouts. By "Absent server" you mean 404 errors? Or no-internet errors? All handled in the catch block, so try triggering them yourself to test. – David Schumann May 13 '20 at 10:29
2

One way of handling axios error for response type set to stream that worked for me.

.....
.....
try{
   .....
   .....
   // make request with responseType: 'stream'
   const url = "your url";
   const response = axios.get(url, { responseType: "stream" });
   // If everything OK, pipe to a file or whatever you intended to do
   // with the response stream
   .....
   .....
} catch(err){
  // Verify it's axios error
  if(axios.isAxios(err)){
    let errorString = "";
    const streamError = await new Promise((resolve, reject) => {
      err.response.data
        .on("data", (chunk) => {
           errorString += chunk;
          }
        .on("end", () => {
           resolve(errorString);
         }
      });
    // your stream error is stored at variable streamError.
    // If your string is JSON string, then parse it like this
    const jsonStreamError = JSON.parse(streamError as string);
    console.log({ jsonStreamError })
    // or do what you usually do with your error message
    .....
    .....
  }
  .....
  .....
}
   
  
Bikash
  • 160
  • 1
  • 6
0

If I understand you correctly, you want some kind of global handler, so you don't have to attach a catch handler to every request you make. There is a window event for that called unhandledrejection.

You can read more about this Event in the official documentation: https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event

Here is how you can attach a listener for this Event:

window.addEventListener('unhandledrejection', (event) => {
  // Handle errors here...
});
David
  • 51
  • 1
  • 6
0
let response;
await axios({ method, url, data: body, headers })
.then(data => { response = data })
.catch(error => { response = error.response; });

You'll get all type of responses in this response object, no need to worry about axios errors, you can handle things based on the response.status

Aslam Shaik
  • 1,629
  • 1
  • 11
  • 10
0

I've been in a scenario where I didn't have access to the back-end of the application and a tool was used to validate the fields, returning something like response.data:

"Error: Username 'UsuarioTeste' is already taken.."

As it was a standard message, I needed to adapt it to present it to the user, I used the process below:

.catch((error) => {
      const errosCadastro = error.response.data;
      const listaErros = errosCadastro.split("\n");
      listaErros.map(erro => {
        if (erro.includes("Error")) {
          const start = erro.indexOf(":") + 2;
          const end = erro.indexOf("'", start) - 1;
          const fieldName = erro.substring(start, end);
          const ErroCampo =
            fieldName == "Username" ? `Já existe um usuário cadastrado com o nome: <span style="font-weight: bold;"> ${this.name}</span>. <br>`
              : fieldName == "Email" ? `Já existe um usuário cadastrado com o email: <span style="font-weight: bold;"> ${this.email}</span>. <br>`
                : fieldName == "registro" ? `Já existe um usuário cadastrado com o registro: <span style="font-weight: bold;"> ${this.record}</span>. <br>`
                  : "";

          errorMessage = errorMessage.concat(ErroCampo);
        }
      })
Lucas Souza
  • 91
  • 1
  • 5