I'm trying to create a scheme to intercept and handle requests from an API middleware, however, for whatever reason I'm unable to properly handle non JSON responses from my API endpoint. The following snippet works just fine for server responses formatted in JSON however say an user has an invalid token, the server returns a simple Unauthorized Access response that I'm unable to handle even though I am supplying an error callback to the json() promise. The Unauthorized Access response message is lost in the following scheme.
const callAPI = () => { fetch('http://127.0.0.1:5000/auth/', {
method: 'GET',
headers: {
'credentials': 'include',
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': 'Basic bXlKaGJHY2lPaUpJVXpJMU5pSXNJbVY0Y0NJNk1UUTVPRE15TVRNeU5pd2lhV0YwSWpveE5EazRNak0wT1RJMmZRLmV5SnBaQ0k2TVgwLllFdWdKNF9YM0NlWlcyR2l0SGtOZGdTNkpsRDhyRE9vZ2lkNGVvaVhiMEU6'
}
});
};
return callAPI().then(res => {
return res.json().then(responseJSON => {
if(responseJSON.status === 200){
return dispatch({
type: type[1],
data: responseJSON,
message: success
});
} else if(responseJSON.status === 401) {
return dispatch({
type: type[2],
message: responseJSON.message
});
}
return Promise.resolve(json);
}, (err) => {
console.log(err.toString(), ' an error occured');
});
}, err => {
console.log('An error occured. Please try again.');
});
Try using text method of Body: res.text().
Try to wrap your response handling code in a try...catch block like this:
return callAPI().then(res => {
try {
return res.json().then(responseJSON => {
[...]
catch(e) {
console.error(e);
}
});
Body.json() throws when the body is actually not JSON. Therefore, you should check if the body contains JSON before you call json() on it. See https://developer.mozilla.org/en-US/docs/Web/API/Response.
Related
I was writing a little wrapper for fetch method in JavaScript (I am very aware of libs like Axios that can do the same thing). I got the idea from a blog post
My code looks like this
async function apiCall(
endpoint,
{ data, headers: customHeaders, ...customConfig } = {}
) {
console.log("endpoint", endpoint);
const config = {
method: data ? "POST" : "GET",
body: data ? JSON.stringify(data) : undefined,
headers: {
"content-type": data ? "application/json" : undefined,
...customHeaders
},
...customConfig
};
return window.fetch(endpoint, config).then(async (response) => {
if (response.ok) {
return response.json();
} else {
// By default, window.fetch will only reject a promise if the actual request itself failed (network error), not if it returned a "Client error response".
const error = await response
.json()
.catch(() => new Error("invalid json"));
return Promise.reject(error);
}
});
}
export function requestMovies(query) {
const endpoint = `${apiULR}?apikey=${API_KEY}&s=${encodeURIComponent(query)}`;
return apiCall(endpoint);
}
However, I encountered TypeError Failed to fetch which I believed is caused by CORS.
If I take out config from window.fetch as in
async function apiCall(
endpoint,
{ data, headers: customHeaders, ...customConfig } = {}
) {
return window.fetch(endpoint).then(async (response) => {
if (response.ok) {
return response.json();
} else {
// By default, window.fetch will only reject a promise if the actual request itself failed (network error), not if it returned a "Client error response".
const error = await response
.json()
.catch(() => new Error("invalid json"));
return Promise.reject(error);
}
});
}
The problem would be gone. Not sure which part exactly triggered this CORS problem...
Here is a live demo: https://codesandbox.io/s/charming-saha-4c2bh?file=/src/index.js
follow the data not given path:
the ternary goes into the false case
headers gets an entry content-type: undefined
the request gets this header added
request is rejected by api because it contains a content-type header (probably with the string 'undefined' in it)
Solution: Dont use a ternary here, and replace it with an if, to get rid of the undefined entry.
Also: read up on differences between null, undefined values and "has own property" in javascript objects
I have an async fetch call which calls my backend to create a customer with an email address. If successful, the JSON returned is sent to a doNextThing() function.
If the backend returns a non-200 status code it also returns JSON like {"message": "Something went wrong"}. I want to catch the error and send that message to the console.
I've read dozens of slightly similar questions and edged close to an answer. So far I have the below, but if the backend's response was a 403 status code, for example, then the console outputs "FORBIDDEN". I think this is because the promise hasn't yet resolved, so doesn't yet have the full JSON response. Or something. But I can't work out what I'm missing.
async function createCustomer(email) {
return fetch("/api/create-customer", {
method: "post",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({email: email})
})
.then(function(response) {
if (response.ok) {
return response.json();
} else {
return Promise.reject({
status: response.status,
statusText: response.statusText
});
}
})
.then(function(returned_data) {
doNextThing(returned_data);
})
.catch(function(e) {
console.error(e.statusText);
});
}
Personally I recommend the async/await syntax if the version you're using supports it. It really simplifies the code and allows you to easily use the try/catch syntax.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await#Browser_compatibility
It seems it doesn't work in IE though if that's a dealbreaker.
async function createCustomer(email) {
try {
const response = await fetch("/api/create-customer", {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: email })
})
if (response.ok) {
const returnedData = await response.json();
// if doNextThing is an async function, you can await it as well or just return it
doNextThing(returnedData);
} else {
throw {
json: await response.json()
status: response.status,
statusText: response.statusText
};
}
} catch (requestErr) {
// do what you like with the error
// this will be called if you "throw" above or
// if fetch() rejects
if (requestErr.json){
console.error(JSON.stringify(requestErr.json));
}
console.error("Request err:" + requestErr);
}
}
Let me know if that helps.
I am making a network request in a react native project using the fetch api. It works well in normal conditions but when I am offline the catch block just gives as result of the logging error but when I do err.message I get "Network request failed" I was hoping to get some codes. How can I get the possible codes to check for in anticipation of a network failure?
I tried logging the entire err and I get nothing but when I use err.message I get "Network request failed"
fetch(url, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
}
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({ procesing: false });
this.setState({ music: responseJson });
})
.catch(err => {
console.log(err)
console.log(err.message)
})
for console.log(err), I expected to have and error code and the corresponding message but I get nothing
If I understand correctly, you are expecting error for some testing purposes.
First of all, try to console.log any string, like
console.log('error');
If nothing happens (I am pretty sure it won't), try throwing error if response is not okay.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
fetch("http://httpstat.us/500")
.then(handleErrors)
.then(function(response) {
console.log("ok");
}).catch(function(error) {
console.log(error);
});
Source: https://www.tjvantoll.com/2015/09/13/fetch-and-errors/
As per this answer, you can use a function to stringify the error:
var stringifyError = function(err, filter, space) {
var plainObject = {};
Object.getOwnPropertyNames(err).forEach(function(key) {
plainObject[key] = err[key];
});
return JSON.stringify(plainObject, filter, space);
};
stringifyError(someError, null, '\t');
I need to catch error 401 Code of response so that I can retry after getting a new token from token endpoint. I am using fetch method get data from API.
const request: Request = new Request(url.toString(), {
headers: this.defaultRequestHeaders,
method: "get",
mode: "cors"
});
const headers: Headers = new Headers({
"Accept": "application/json",
"Content-Type": "application/json"
});
fetch(request)
.then(function(response)
{
///Logic code
})
.catch(function(error)
{
///if status code 401. Need help here
});
You can check the status and if it's not 200 (ok) throw an error
fetch("some-url")
.then(function(response)
{
if(response.status!==200)
{
throw new Error(response.status)
}
})
.catch(function(error)
{
///if status code 401...
});
Because 401 is actually a valid response to a request to a server, it will execute your valid response regardless. Only if security issues occur, or if the server is unresponsive or simply not available will the catch clause be used. Just think of it like trying to talk to somebody. Even if they say "I am currently not available" or "I don't have that information", your conversation was still successful. Only if a security guy comes in between you and stops you from talking to the recipient, or if the recipient is dead, will there be an actual failure in conversation and will you need to respond to that using a catch.
Just separate out your error handling code so you can handle it in instances that the request was successful, but does not have the desired outcome, as well as when an actual error is being thrown:
function catchError( error ){
console.log( error );
}
request.then(response => {
if( !response.ok ){
catchError( response );
} else {
... Act on a successful response here ...
}
}).catch( catchError );
I am using the response.ok suggested by #Noface in the comments, as it makes sense, but you could check for only the response.status === 401 if you want to.
You can try this
fetch(request)
.then(function(response) {
if (response.status === 401) {
// do what you need to do here
}
})
.catch(function(error) {
console.log('DO WHAT YOU WANT')
});
You can check the status of the response in then:
fetch(request)
.then(function(response) {
if (response.status === 401) {
// do what you need to do here
}
})
.catch(function(error) {});
fetch(url,{
method: 'GET',
headers,
body: JSON.stringify(aData)
}).then(response => {
if(response.ok){
return response.json();
}
return Promise.reject(response);
}).catch(e => {
if(e.status === 401){
// here you are able to do what you need
// refresh token ..., logout the user ...
console.log(e);
}
return Promise.reject(e.json());
});
(function () {
var originalFetch = fetch;
fetch = function() {
return originalFetch.apply(this, arguments).then(function(data) {
someFunctionToDoSomething();
return data;
});
};})();
source
Can one use the Fetch API as a Request Interceptor?
When you want to...
catch (error) {
console.dir(error) // error.response contains your response
}
I am sending a status code 422 from my backend code with response body which contains the description of the error. I am using axios post as below to post a request:
post: function(url, reqBody) {
const request = axios({
baseURL: config.apiUrl,
url: url,
headers: {
'Content-Type': 'application/json',
'Authorization': sessionStorage.getItem('token')
},
method: 'POST',
data: reqBody,
responseType: 'json'
});
return request
.then((res) => {
return res;
})
.catch((error) => {
console.log(error);
return error;
})
}
The problem is when backend is returning error code 422, the error object I am catching has no information about response body. Is there any way I can retrieve the error text?
I had this same issue and the answer (as per Axios >= 0.13) is to specifically check error.response.data:
axios({
...
}).then((response) => {
....
}).catch((error) => {
if( error.response ){
console.log(error.response.data); // => the response payload
}
});
See here for more details.
The "body" of an AXIOS error response depends from the type of response the request had.
If you would like full details about this issue you can see this blogpost: How to catch the body of an error in AXIOS.
In summary AXIOS will return 3 different body depending from the error:
Wrong request, we have actually done something wrong in our request (missing argument, bad format), that is has not actually been sent. When this happen, we can access the information using error.message.
axios.get('wrongSetup')
.then((response) => {})
.catch((error) => {
console.log(error.message);
})
Bad Network request: This happen when the server we are trying to reach does not respond at all. This can either be due to the server being down, or the URL being wrong.
In this case, we can access the information of the request using error.request.
axios.get('network error')
.then((response) => {})
.catch((error) => {
console.log(error.request );
});
Error status: This is the most common of the request. This can happen with any request that returns with a status that is different than 200. It can be unauthorised, not found, internal error and more. When this error happen, we are able to grasp the information of the request by accessing the parameter specified in the snippets below. For the data (as asked above) we need to access the error.response.data.
axios.get('errorStatus')
.then((response) => {})
.catch((error) => {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
})
For those using await/async and Typescript
try {
const response = await axios.post(url, body)
} catch (error) {
console.log(error.response.data);
}
For react native it just worked for me
api.METHOD('endPonit', body)
.then(response => {
//...
})
.catch (error => {
const errorMessage = JSON.parse(error.request.response)
console.log(errorMessage.message)
})
We can check error.response.data as #JoeTidee said. But in cases response payload is blob type? You can get error response body with the below code.
axios({
...
}).then((response) => {
....
}).catch(async (error) => {
const response = error.response
if(typeof response.data.text === function){
console.log(await response.data.text()); // => the response payload
} else {
console.log(response.data)
}
});
I am returning a string from backend but expecting a json as response type. So I need to return an object instead of string for axios to process it properly.
In my case I wanted to retrieve a response 404 error message (body).
I got body with error.response.data but I couldn't display it because the type was ArrayBuffer.
Solution:
axios.get(url, { responseType: 'arraybuffer' }).then(
response => {...},
error => {
const decoder = new TextDecoder()
console.log(decoder.decode(error.response.data))
}
)
Related posts:
Converting between strings and ArrayBuffers