Why do I get "Fetch failed loading" when it actually worked? - javascript

I use the following code to POST the users position to my own backend-service via the Fetch API:
window.onload = () => {
let getPosition = (options) => {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, options);
});
};
getPosition().then(pos => {
const data = new FormData();
data.append('latitude', String(pos.coords.latitude));
data.append('longitude', String(pos.coords.longitude));
fetch('/', { // <--- error is thrown in this line
method: 'POST',
body: data
}).then(response => {
if (!response.ok) {
throw Error('Data sent - Network response NOT OK');
} else {
console.log('Data sent - Network response OK')
}
});
});
};
This works flawlessly and the backend-service sends back a positive empty response. Data sent - Network response OK is logged in the browser's console and everything is fine except that immediately after, this is logged:
How come I get a Fetch failed loading: POST even though the POST succeeded, the response is OK and the status code in the Chrome network tab is 200 OK? Is there something wrong with my code?

I had the same behaviour that you were seeing.
My server responded to the POST request with a 204 (Empty Response) and an empty (Content-Length=0) response.
I tried changing that to just respond with a "ok" message instead (edit: actually just returning the created object now) and the fetch error log disappeared.
It seems to me that fetch (at least in Chrome) erroneously logs "Fetch Failed" when the response body is empty, even if the request was successful.

This happens because you don't read the empty response body. I noticed this, because Django triggers a broken pipe error on the server side. To fix this, just read the empty body out.
async function fetchSomething() {
const response = await fetch(url, {
method: 'POST'
})
await response.text()
return response.ok
}

Related

Error: SyntaxError: Unexpected end of JSON input at fetch.then.response

I get this error every time I try to use the POST method in my API.
SyntaxError: Unexpected end of JSON input at fetch.then.response
When I use the GET method I get the data normally.
This is the code:
const teste = () => {
fetch("myURL/test", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
id: 1,
name: "Teste",
amount: 1,
value: 3
})
})
.then(response => response.json()) //Here is the error
.then(data => {
console.log(data);
})
.catch((err)=>console.log(err))}
Can someone help me? Thank you.
EDIT:
I just add this line to see the log:
.then(response => console.log(response))
Here is what I got:
Response {
type: "cors",
url: "myURL/test",
redirected: false,
status: 201,
ok: true,
…}
body: (...)
bodyUsed: false
headers: Headers {}
ok: true
redirected: false
status: 201
statusText: ""
type: "cors"
: "myURL/test"
__proto__: Response
It means that the page fetched at myURL/test does not responds with JSON content, or with malformed JSON content. This is not an error in your script, which is fine, this is an error with your server, that is not serving JSON content at myURL/test.
Also, note that servers might not respond similarly to GET requests and POST request for the same URL! If you fetch valid JSON from a GET request but, as you described, failed to fetch valid JSON from a POST request, your server might be serving different content depending on the request type. Investigate that.
Debug tips
Replace then(resp => resp.json()) by then(resp => resp.text()).then(console.log) to see what the served content looks like
Use Postman to simulate API calls, and see what the served content looks like, with more details
Examine the response with the developer tools:
In Chrome
Open the console (F12)
Go to the network tab
Click on the file server by myURL/test
Click on response: that will be the text content. It shoud be properly formatted JSON.
Basically GET method does not send your body object to the server in which you get the response. Only the POST action will send your body object to the server.
I assume that the object you wish to send is the problem. Either the server which you are trying to connect does not expects the body object as string or you should make sure you have parsed the JSON properly before processing.
Looks like the API you're calling, doesn't have a response body on this particular POST. Then when you call response.json() (converting response.body to json) it runs into error.
Or maybe the response is body is not a valid json body.
If you wanna handle this error more fashion way you could go like this:
tryGetJson = async (resp) => {
return new Promise((resolve) => {
if (resp) {
resp.json().then(json => resolve(json)).catch(() => resolve(null))
} else {
resolve(null)
}
})
}
https://github.com/github/fetch/issues/268#issuecomment-399497478
(for people coming later but dealing with this problem)
The problem is most probably server error or invalid URL but you can't see it because all examples on internet how to work with fetch are missing one important part - the server or network failure.
I think the correct way how to deal with fetch is test response if contains errors before conversion to json.
Check the part of the first then in example where it.ok is tested:
async function fetchData() {
return await fetch('https://your-server.com/some-NOt-existing-url/')
.then(it => {
if (!it.ok) {
throw `Server error: [${it.status}] [${it.statusText}] [${it.url}]`;
}
return it.json();
})
.then(receivedJson => {
// your code with json here...
})
.catch(err => {
console.debug("Error in fetch", err);
setErrors(err)
});
}
(note: it is just name convention borrowed from Kotlin, it makes JavaScript code shorter. It is alias for anything on left side of expression so in this case for response)

javascript fetch does not catch 404 error

I have an app with uses a fetch to retrieve information from a 3rd party API. I am calling the API via cors-anywhere-herokuapp.com. When I call the API with valid data, I am able to retrieve the API response and pick up the data I need to pick up. When I call the API with invalid data, the API returns a 404 server error. I am forcing invalid data so I can code to catch this situation. My problem appears to be that the fetch does not catch the 404 error. I have tried coding to inspect the return.status but to no avail. I tried testing return.ok and that didn't work either. This is the code I am executing:
function showProduct(barcode) {
let url = "https://cors-anywhere.herokuapp.com/http://api.barcodelookup.com/v2/products?barcode=" + barcode + "&key=mj1pm32ylcctxj1byaia85n9dk2d4i";
url = "https://cors-anywhere.herokuapp.com/http://api.barcodelookup.com/v2/products?barcode=5000159459211&key=mj1pm32ylcctxj1byaia85n9dk2d4i";
const options = { method: 'GET' };
fetch( url, options)
.then(function(response) {
return response.json();
})
.then(function(myJson) {
if (myJson == undefined)
{
console.log("fetch failed")
}
else
{
//inspect the data that the WebAPI returned
document.getElementById("showScanner").style.visibility = "hidden";
document.getElementById("scanner-container").style.visibility = "hidden";
document.getElementById("showProductDiv").style.visibility = "visible";
document.getElementById("productManufacturer").innerHTML = myJson.products[0].manufacturer;
document.getElementById("productName").innerHTML = myJson.products[0].product_name;
document.getElementById("productDescription").innerHTML = myJson.products[0].description;
Quagga.stop();
}
});
}
and this is what I see in the debugger when I execute the code
When I look at the debugger network tab, and click on Headers, I see this:
So, my question is, how do I capture the status code?
According to the documentation on fetch's response, you should be able to testthe status code of the response that fetch's Promise resolves to using response.status.
Note that although it's unintuitive, the Promise fetch returns only rejects when there's a connection error. If the connection succeeds and there's a response, this promise will resolve with the response, even if the status code is non-200.

How to handle error status in ES6 Promises?

I am using ES6 and trying to handle error status of the fetch() result.
I know I can catch error like this:
fetch(`http://${this.serverAddress}/reset`)
.then((resp) => {
Logger.debug(JSON.stringify(resp));
})
.catch((err) => {
Logger.debug(JSON.stringify(err));
});
In catch block I take an Error with message "Failed to fetch". There is no status information.
I have found the following suggestion:
fetch('/some/url/')
.then(processResponse)
.then(response => {
// do something
})
.catch(response => {
// repsonses with status >= 400 get rejected. you can access response.status and response.data here too
if (response.status === 400) {
// handle form validation errors, response.data.errors...
} else if (response.status === 403) {
// handle permission errors
} // etc
});
But response.status is undefined in catch block.
How can I get status code of the error to handle it?
UPDATE:
Thank you for the responces. Please see my updates:
My actual token looks as http://api.service/v2/prices/latest?token=err. If I write my token instead of err, then it works. When I try this URL, my code go to the catch block, not then.
I noticed the following error in browser console:
No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access. The response had HTTP status code 401. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
If I add {mode: "no-cors"}, code go to the then, but status is always 0. This is not that I want.
I tried to add
mode: "cors",
headers: {
"Access-Control-Allow-Credentials": true,
"Access-Control-Allow-Origin": "*",
"Content-Type": "application/json"
}
This is not help, I still have No 'Access-Control-Allow-Origin' header error.
You may try to handle the error status by throwing error during response processing right after the fetch is done with !ok:
fetch('/some/url/')
.then(response => {
if (!response.ok) {
throw Error({ status: response.status });
}
response.json();
})
.then(response => {
// do something
})
.catch(error => {
switch(error.status) {
// ...
}
});
You can't check the status in code.
The Promise returned from fetch() won’t reject on HTTP error status
even if the response is an HTTP 404 or 500. Instead, it will resolve
normally (with ok status set to false), and it will only reject on
network failure or if anything prevented the request from completing.
Basically, fetch() will only reject a promise if a networking error occurs.
The fetch API provides a simple ok flag that indicates whether an HTTP response’s status code is in the successful range or not. See the below example
fetch("http://httpstat.us/500")
.then(function(res) {
if (!res.ok) {
throw Error(res.statusText);
}
return res;
}).then(function(res) {
console.log("ok");
}).catch(function(error) {
console.log(error);
});
After the fetch request check if response is as expected. If not, throw new error with a custom message. In the catch check if this custom message is there for the thrown error. Handle accordingly.
You can also create custom Errors and check the same.
As seen in the docs the catch block will be reached only if the request didn't complete. When there is a status code available (and all the other data is received as well) fetch will resolve and therefore the then block will be reached.
The Promise returned from fetch() won’t reject on HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing. - Source
So all you have to do is logging resp.status instead of resp.
fetch(`http://${this.serverAddress}/reset`)
.then((resp) => {
Logger.debug(JSON.stringify(resp.status));
})
.catch((err) => {
Logger.debug(JSON.stringify(err));
});
In catch block I take an Error with message "Failed to fetch". There is no status information.
So in case the catch block is getting called the request didn't even finish. This might be caused because you are not using options like method, headers,... If needed, which one and what values depends on the backend you are using.

Wrapping node.js request into promise and piping

Here is my scenario:
I want to get some external resource (binary file) using request library and pipe it to the client of my own application. If response code is != 200 or there are problems reaching remote server, I want to intercept and provide custom error message instead. Ideally, if response is fine, I want original headers to be preserved.
I was able to achieve that with the first piece of code I've pasted below. However, my whole application is based on Promise API so I wanted to make it consistent and wrap it in promise too. And when I do that, it no longer works. Firstly, I tried to achieve that with request-promise, without success. Then I tried to prepare very simple example on my own, still no luck.
Working example
var r = request.get('http://foo.bar');
r.on('response', result => {
if (result.statusCode === 200) {
r.pipe(res);
} else {
res.status(500).send('custom error message')
}
});
r.on('error', error => res.status(500).send('custom error message');
Not working example
var r;
return new Promise((resolve, reject) => {
r = request.get('http://foo.bar');
r.on('response', result => {
if (result.statusCode === 200) {
resolve();
} else {
reject()
}
});
r.on('error', reject);
}).then(() => {
r.pipe(res);
}).catch(() => {
res.status(500).json('custom error message');
});
By 'not working' I mean - no response is delivered, request is pending until timeout.
I've changed the code to call .pipe() on result passed to resolve instead of r. It responds to client, but response is empty then.
At the end, I've tried replacing request lib with simply http.get(). And with that, server returns file to the client, but headers (like Content-Type or Content-Length) are missing.
I've googled a lot, tried several request versions... and nothing is working.
The problem is that when "response" is triggered, you create a new promise that resolves immeadiately, but the then callback is always executed asynchronously, and when it gets called the file has arrived at the server, and there is no data flowing through the stream anymore. Instead you could just use the body parameter of the callback:
request.get('http://foo.bar', function(request, response, body) {
if(response.statusCode === 200) {
res.end(body);
} else {
res.status(500).end();
}
});
For working with streams request seems a bit buggy, axios seems to do it better:
axios.get("http://foo.bar"', {
validateStatus: status => status === 200,
responseType: "stream"
}).then(({data: stream}) => {
stream.pipe(res);
}).catch(error => {
res.status(500).json(error);
});

Error bubbles to console even though it is handled

I am using isomorphic-fetch to perform AJAX requests from my react-redux application. In my api middleware I have the following function which calls the external resource:
import fetch from 'isomorphic-fetch';
function callApi({ endpoint, method, body, params = {} }) {
let route = generateRoute(endpoint, params);
return fetch(route, generateFetchOptions(method, body))
.then(response => {
if (!response.ok) {
return Promise.reject(response);
}
return response.json();
});
}
The above function is called by the following piece of code:
return callApi(callAPI).then(
response => next(actionWith({
response,
type: successType,
statusCode: 200
})),
error => error.json().then(errorObject => {
return next(actionWith({
type: failureType,
statusCode: errorObject.statusCode,
error: errorObject.message || 'Something bad happened'
}));
})
);
If I reject with Promise.reject(response) the error is being handled by the error handler, but for some reason the error also bubbles to the browser console (in my case Chrome).
Here is a screenshot from the console which shows what is happening (api.js:34 is the second line of the callApi method):
This is the usual behavior (in probably every browser?) when hitting an error during an HTTP request (no matter whether a linked image cannot be found, or an XHR fails). No matter if and how you handle those errors, they will always be logged to the console. There is no way to suppress this behavior.
References:
Provide a way not to display 404 XHR errors in console
How can I stop jQuery.ajax() from logging failures to the console?

Categories