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
Related
I have the following React Native client code:
confirmMatchRecord(userId, matchedUserData['userId'], matchRecordData['matchRecord']['matchWinner'], matchRecordData['matchType'], currentUserRating, matchedUserRating, matchData['_id'], matchRecordData['matchRecord']['_id'], airbnbRatingValue, true, new Date())
.then((results) => {
// Do stuff
})
.catch((error) => {
Alert.alert('Error', 'There was an issue with confirming the record. Please check your connection and/or try again later.');
});
And the following code in my confirmMatchRecord function:
export async function confirmMatchRecord(userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp) {
console.log('Attempting to record match');
info = { userId, matchedUserId, matchWinner, matchType, currentUserRating, matchedUserRating, matchId, matchRecordId, matchRating, matchConfirmed, timestamp }
const firebaseIdToken = await AsyncStorage.getItem('#firebaseIdToken')
const requestOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + firebaseIdToken },
body: JSON.stringify(info)
};
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (response['Status']==='Failure') {
// throw new Error(`HTTP error! status: ${response.status}`);
throw new Error(400);
} else if (response['Status']==='Success') {
const data = await response.json()
return data
}
}
Server code:
router.post('/confirmmatchrecord', async (req, res) => {
// Do a lot of stuff
if (response==='Success') {
return res.status(200).json({'Status': 'Success'})
} else {
return res.status(400).json({'Status': 'Failure'})
console.log('Match record was not confirmed successfully');
}
When response['Status']==='Failure (sent by server) it throws an error 400 as you can see, I was hoping to trigger the .catch in the client code then. But that does not happen, because the client code continues to run on the .then part.
How should I do this instead? Not sure if using .catch here is even correct or if I should do this type of work another way.
You seem to be aware of the bit of a footgun in the fetch API (I write about it here) where fetch only rejects its promise on network errors, not HTTP errors, but your check is incorrect in a couple of ways:
It's status, not Status (capitalization matters), and
It's the HTTP code (400 for instance), not a string
The Response object provides a convenient ok flag that's true for any successful response and false otherwise, so:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
const data = await response.json();
return data;
In a comment you've said:
My response['Status'] was checking for a custom server message I had sent (res.status(400).json({'Status': 'Failure'}), I updated the post with it. Not sure why it didn't catch that
Ah! Okay. The reason it didn't catch it is that you're looking for it on the Response object, but your JSON is in the response body.
I suspect you don't want to use your own Status anymore since you know about response.ok and response.status now, but if you ever do want to include your own information in an error response as JSON, you can do that. You'd do it like this:
const response = await fetch(ngrokOrLocalhost + '/confirmmatchrecord', requestOptions)
const data = await response.json(); // Expects JSON in *both* the success response and the error response
if (data.Status === "Failure") {
throw new Error(`HTTP error! status: ${response.status}`); // This will be "HTTP error! status: 400" if the HTTP error code is 400
}
return data;
But I'd stick with just the built-in ok and status for pure success/failure information. This could be handy if you wanted to provide more details of the failure, though.
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 trying to load a data from my firebase backend but getting this error
Uncaught (in promise) TypeError: response.json is not a function
my code is as below :
import axios from 'axios';
export const loadData = ({ commit }) => {
console.log('getting data from server...');
axios
.get('data.json')
.then(response => response.json())
.then(data => {
if (data) {
const stocks = data.stocks;
const stockPortfolio = data.stockPortfolio;
const funds = data.funds;
const portfolio = {
stockPortfolio,
funds
};
commit('SET_STOCKS', stocks);
commit('SET_PORTFOLIO', portfolio);
console.log('Commit done ');
}
});
};
however, if I try response.data instead of response.json it works and successfully loads the data, so I am curious what the difference is and why the first one doesn't work.
Because of the axios package, it has a specific response schema https://github.com/axios/axios#response-schema
The response for a request contains the following information.
{
// `data` is the response that was provided by the server
data: {},
// `status` is the HTTP status code from the server response
status: 200,
// `statusText` is the HTTP status message from the server response
statusText: 'OK',
// `headers` the headers that the server responded with
// All header names are lower cased
headers: {},
// `config` is the config that was provided to `axios` for the request
config: {},
// `request` is the request that generated this response
// It is the last ClientRequest instance in node.js (in redirects)
// and an XMLHttpRequest instance in the browser
request: {}
}
With axios you don't need an extra .json() .Responses are already served as javascript object, no need to parse, simply get response and access data. You could use directly something like
axios.get('/user/12345')
.then(function (response) {
console.log(response.data);
console.log(response.status);
console.log(response.statusText);
console.log(response.headers);
console.log(response.config);
});
You only need to use the Body.json() method if you are trying to resolve the promise from a Response stream. You may read more about it on the documentation. One use case of doing so would be when you are making a HTTP request using the fetch API, whereby you will have to call Body.json() to return the response body.
let response = await fetch(url);
if (response.ok) { // if HTTP-status is 200-299
// get the response body (the method explained below)
let json = await response.json();
} else {
alert("HTTP-Error: " + response.status);
}
For axios, you only need to resolve the promose after making a GET request
axios.get(url[, config])
and thus, the following code below works, as the returned response body is handled within the .then() block when you resolve the promise.
axios
.get('data.json')
.then(response => console.log(response.data))
const CollectData = async () => {
let result = await fetch('http://localhost:5400/Enquiry', {
method: "post",
body: JSON.stringify({ name, email, contact, message }),
headers: {
"Content-Type": "application/json",
}
});
result = await result.json();
console.log(result);
if (result) {
navigate("/");
}
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
I have the following code for making POST Requests.
I'm not 100% sure about error handling here, but it was important for me that I get body text when request is not successful.
One issue that I still do have is - if server responds with 200 OK but invalid json - can I log that payload?
What would be the correct way of logging for Fetch?
Fetch(data.notificationUrl, {
method: 'POST',
body: post_data,
headers: {
'Content-Type': 'application/json'
}
}).then((res) => {
if (!res.ok) {
// Could reject the promise here but than response text wouldn't be available
//return Promise.reject(`Response was not OK. Status code: ${res.status} text: ${res.statusText}`);
return res.text().then((txt) => `Response was not OK. Status code: ${res.status} text: ${res.statusText}.\nResponse: ${txt}`);
}
// response ok so we should return json, could follow https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch and determine the payload type by content-type header...
return res.json();
}).then((response) => {
if (response) {
// set result
// ...
// redirect
return reply.redirect(data.redirectUrlDirectory);
}
return reply(Boom.preconditionFailed(`Did not reply with correct payload! json:'${JSON.stringify(response)}'`));
}).catch((err) => {
return reply(Boom.badData(`Could not notify on url ${data.notificationUrl} about the payment ${id}.\nError: "${err}"`));
});
I would use something like this.
This fist option asumes your service response always the header "application/json" and a the pay load simple text which I mock it like this.
var app = new express();
app.get('/valid', function(req, res){
res.json({ok: "ok"});
});
app.get('/invalid', function(req, res){
res.json("bad json body");
});
and the fetch json handling should looks like this. The other part of your code looks like good for me.
var response2 = res.clone();
return res.json().then((json) => {
// log your good payload
try {
// here we check json is not an object
return typeof json === 'object' ? json : JSON.parse(json);
} catch(error) {
// this drives you the Promise catch
throw error;
}
}).catch(function(error) {
return response2.text().then((txt) => `Response was not OK. Status code: ${response2.status} text: ${response2.statusText}.\nResponse: ${txt}`);
//this error will be capture by your last .catch()
});
xxx.clone() allows you to resolve multiple times the same response and create your own combinations like the previous one.