Fetch API: Can 'await res.json()' fail after a completed request? - javascript

A fetch API request will only fail if there is a network or server error. So for example, if I execute the following code, assuming it went through the try block without an error, I will have a valid populated res.
try {
const res = await fetch('/createurl', {
method: 'POST',
body: 'testData',
headers: {
'Content-Type': 'application/json'
}
})
if (res.ok) {
alert('Resource created!')
} else {
alert('Error creating resource!')
}
flashResponseToUser(res)
} catch(e) {
alert('A server or network error occurred during the request!')
}
I am handling res to show the users the necessary error or success message using the flashResponseToUser(res) function. Since res.json() returns a Promise, flashResponseToUser has to be an async function.
const flashResponseToUser = async(res) => {
const jsonRes = await res.json() // Get data from response
console.log(jsonRes)
}
I want to know:
Why does res.json() return a Promise since at this point the response has already been received by the client?
Under what conditions would the Promise returned by res.json() fail?
Does the code within flashResponseToUser(res) also need to be wrapped within a try-catch block since I am using res.json()?

Why does res.json() return a Promise since at this point the response has already been received by the client?
fetch returns a Response object. This indicates that the headers of the response have been received, but does not necessarily mean that the whole response has been received - imagine, for example, when you load a huge page. It's not exactly the same thing, but you'll receive the headers and the browser will start to load the response even though there's still more to download. The Response object provides the headers and a way to handle still-incoming data.
Under what conditions would the Promise returned by res.json() fail?
It might fail if the response wasn't in proper JSON format. For example, if the plain text of the response was Internal Server Error, which isn't JSON. Here's an example:
(async () => {
const response = await fetch('data:,Internal%20Server%20Error');
console.log('got response');
try {
await response.json();
} catch(e) {
console.log('error:', e.message);
}
})();
Does the code within flashResponseToUser(res) also need to be wrapped within a try-catch block since I am using res.json()?
If you wanted to be perfectly safe, yes. But, in most situations, it's easiest just to catch in one place, where you can handle the error. Instead of handling possible errors at every step of the process, you might handle the error just once, in the consumer, eg:
const getInfo = async () => {
const res = await fetch('/createurl', {
method: 'POST',
body: 'testData',
headers: {
'Content-Type': 'application/json'
}
})
if (!res.ok) {
throw new Error(res.status);
}
return res.json();
};
getInfo()
.then(flashResponseToUser)
.catch(() => {
alert('A server or network error occurred during the request!')
})
(assuming that flashResponseToUser will never throw, if provided with an expected object. If flashResponseToUser might throw anyway, you can separate out the .catches to distinguish network errors from other runtime errors)

Related

Fetch fails when exceeding a certain execution time even-though using proper CORS headers

I have been stuck with a weird issue with CORS for quite some time now. To explain the setup I have a frontend JS snippet (including fetch API call to my own server) which I want to use as an embed code snippet other web applications can use. (ex: kind of like google analytics)
// JS snippet to copy to an unknown website
<script>
// extract data and add to the body
const extracted_data = {}
fetch("https://api.xxxx.com/xxxxxx/create", {
method: "POST",
mode: 'cors',
body: JSON.stringify(extracted_data),
referrer: "origin",
headers: {
'Content-Type': 'application/json',
},
})
.then(function (response) {
// The API call was successful!
if (response.ok) {
return response.json();
} else {
return Promise.reject(response);
}
})
.then(function (data) {
// This is the JSON from our response
console.warn("Successfull!", data);
alert("Success:" + JSON.stringify(data));
})
.catch(function (err) {
// There was an error
console.warn("Something went wrong ->", err);
alert("error:" + err.message);
});
</script>
The problem is even if I have set my fetch API as below and the correct CORS headers are in my preflight response from my API it works only when the API call resolves immediately. If the API takes more time Fetch throws this common error even if the preflight is successful.
TypeError: Failed to fetch
I verified it using adding the below code to my API. Then it stops working and throws the above error. When I don't have any time taking functions inside my API call it works without errors.
// 10 sec delay to check async behavior of the API
await new Promise(resolve => setTimeout(resolve, 10000));
Any recommendations on how I should proceed to resolve this?

The else part is not working when the if-statement fails. How can i solve this issue?

async function loginUser(event){
event.preventDefault();
const response = await fetch('http://localhost:3001/api/login/',{
method:'POST',
headers:{
'Content-Type' : 'application/json',
},
body : JSON.stringify({
email,
password,
}),
})
const data = await response.json();
if(data.user){
localStorage.setItem("token",data.user);
// window.location.href = "./CustomerDashboard";
}else{
console.log("Invalid Credentials");
toast.error("Login failed");
}
console.log(data);
}
Here the else statement not working. Also the console.log(data) after the if-statement works only if the if-statement satisfies otherwise it is not executing.
Do console.log( response ) immediately after the fetch to see what you got. You are flying blind right there. You have to look at the whole response object. Once you see what that looks like, you can hit the docs for whatever fetch library you are using and see how to handle response correctly. I've normally used one that had a finally block so if it failed I could do something.
response.json() is guaranteed to fail at some points, no matter how you slice it, because not every api call with in fact return json, or even a body.

How to catch the error when making fetch api request? [duplicate]

This question already has answers here:
How to handle HTTP code 4xx responses in fetch api
(4 answers)
Handle a 500 response with the fetch api
(2 answers)
try..catch not catching async/await errors
(1 answer)
Closed 2 years ago.
I am sending request with fetch api and make action according to the result is correct or it includes error.
my service code:
LoginUser(user: User) {
return fetch("http://localhost:8080/login", {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
method: 'POST',
body: JSON.stringify(user)
});
}
and my then-catch code which calls the above one is:
async loginUser() {
let response = await this._service.LoginUser(this.user)
.then(response => {return response.json()})
.then(response => {this._router.navigate(['/mainpage'])})
.catch(error => {alert(error)});
}
Whether the response is coming with code 500 Internal Server Error still it is redirecting to /mainpage and does not recognise the error. How can I fix this problem ?
When a fetch request comes back with an error code from the server, the catch of the promise is not executed, the then is. The catch is only executed if there is a network failure. See the fetch docs:
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.
What you need to do is, inside your then, check response.ok. If response.ok == false, then the request returned an error. You can find info about this error in response.status and response.statusText.
If you are using async await you shouldnt have to chain on .thens like you are resolving a promise.
I adjusted your code and wrapped it in a try/catch, the try/catch error will handle an error from a non response however you will need to check your server response for errors itself
async loginUser() {
try {
let response = await this._service.LoginUser(this.user)
// Check your response for error this may not be response.error
if (response.error) {
// Handle error
alert(error)
} else {
// Navigate on success
this._router.navigate(['/mainpage'])
}
} catch (err) {
alert(err)
}
}

Redirect REST API response to UI/Browser NodeJs/Express/Request

Not able to send response of a REST api callout to browser page using NodeJs server with Express and Request module.
I understand that due to asynchronous nature of callback method, response of api callout cannot be returned in typical style.
I have tried res.send method, but it gives error res.send is not a function.
Below is sample piece of that I have tried.
const options = {
url: endPoint,
method: 'POST',
headers: {
'Authorization': 'Basic',
'Accept': 'application/json',
'Content-Type': 'application/json',
'Accept-Charset': 'UTF-8'
}
};
request(options, (err, res, body) => {
if (err) { return console.log(err); }
//want to send this body to the page.
console.log(JSON.stringify(body));
res.send(body);
});
It gives this error message,
res.send(body);
TypeError: res.send is not a function
at Request.request [as _callback]
Figured the issue.
First problem was as #vipul pointed above. res was response of callout and not instance of global HttpResponse object, so send method was not available on that object. I changed the method,
request(options, (err, response, body) => {
if (err) { return console.log(err); }
//want to send this body to the page.
console.log(JSON.stringify(response.body));
// using the HttpResponse in global context.
this.res.send(JSON.parse(body));
});
Then I faced below error,
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client]
Problem was while making the callout, I was returning the response right after callout method.
.get('/authorize', (req, res) => {
console.log('Authorize Hit! '+req.query.code);
try {
//TODO: Need to utilize state to the current flow in action.
let flowDetails = flow.flowMap.get('wsf');
if(flowDetails) {
//this method makes REST API call
flowDetails.refreshToken(req, res);
//this line was the problem!! Removed it to fix the error.
res.send('Refresh Token Completed!');
}
} catch (error) {
(Object.keys(error).length === 0)?res.send(error.message):res.send(error);
}
})
But due to asynchronous nature of callback function, by the time callout response was received, actual request/response was already returned and request was completed. So removed the res.send right after callout to fix the issue.
Hopefully it will be helpful for others as well :)

Getting API response payload using fetch

I am using fetch to get the API response for GET and POST requests. When an error occurs, I am able to see the status code and the text i.e, 400 Bad Request. However, there is additional information being passed that explains why the error was thrown (i.e. username did not match). I can see this additional message in the response payload via Firefox developer tool's console but I am not sure how to get it via handling the fetch response.
Here's an example request:
fetch(url, {
method: 'POST',
body: JSON.stringify({
name: name,
description: description
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
"Authorization": "Bearer " + token
}
}).then(response => {
if (!response.ok) {
throw Error(response.statusText)
}
return response
})
.catch(error => {
console.log(error)
})
Any ideas? Thanks.
Thank you everyone for your suggestions.
This tutorial helped me understand what to do.
https://css-tricks.com/using-fetch/
My problem was that when there is an error, the response is not JSON, it's text. So I needed to do something like this (taken from css-tricks.com):
fetch('https://api.github.com/users/chriscoyier/repos')
.then(response => response.text())
.then(data => {
console.log(data)
});
You seem to be passing only the statusText field of the response, which corresponds to the HTTP status code (And not the response body) - for example Bad Request for HTTP response code 400.
You can read the response body using one of the methods defined on the Response object returned by the fetch API. For example, if you're expecting a JSON response body, you can have:
const onSuccess = response => {
// Do something with the response
// What you return from here will go to the next .then
}
const onFailure = response => {
// response.json() returns a promise that resolves to the JSON sent in the body
// Note that whatever is returned from here will go to the next .then
// To go to the next .catch, you can throw from here
return response.json().then(jsonResponse => console.log(jsonResponse))
}
fetch(url, {
method: 'POST',
body: JSON.stringify({
name: name,
description: description
}),
headers: {
"Content-type": "application/json; charset=UTF-8",
"Authorization": "Bearer " + token
}
}).then(response => {
if (!response.ok) {
throw response
}
return response
})
.then(onSuccess, onFailure)
.catch(err => { /* Any error thrown from the handlers will be caught here */ })
You can have a look at the Response object documentation for more details.
Based off the docs, I'd do something more along the lines of this:
const response = await fetch('http://example.com/movies.json')
const myJson = await response.json();
console.log(JSON.stringify(myJson));
Otherwise you have to do everything inside of your .then().
In regards to the additional text you are looking for, that's totally dependent on the response object, and I have no way of knowing without seeing it.
#Voxum, your answer is missing important info, like a method..and ; await is good, but remember it should be in an async function, and you dont need to use it if you "thenify" .then() as that returns the promise. from the Fetch docs, that is their basic get/HTML example. i think the OP is asking for a API call for different types of methods, which will require a more advanced setup.
The thing is with a 400 response, the server is not giving you a response message as the 404 (for example) is telling you the page is not found. Usually the only time a server will give you a response message is when you get a good (success/200). there will usually be a message at response.json() or response.text() depending on your data coming back.
after you call fetch with the url, method and any headers use
.then((response) => {console.log(response.json());}); for json and use
.then((response) => {console.log(response.text());}); for xml/text
OP has the fetch set up properly but just needs to use response.json() or response.text(). again, a 200 response can still be a "incorrect password" and this is where you'll use this. don't expect a response body on the 400/500s. good luck!

Categories

Resources