fetch response.json() and response.status - javascript

Is this the only way to use the body.json() and also get the status code?
let status;
return fetch(url)
.then((response => {
status = response.status;
return response.json()
})
.then(response => {
return {
response: response,
status: status
}
});
This doesn't work as it returns a promise in the response field:
.then((response)=> {return {response: response.json(), status: response.status}})

Your status is not visible in the second then. You can just get the two properties in the single then.
json() returns a new Promise to you, so you need to create your object inside the then of the result of that function. If you return a Promise from a function, it will be fulfilled and will return the result of the fulfillment - in our case the object.
fetch("https://jsonplaceholder.typicode.com/posts/1")
.then(r => r.json().then(data => ({status: r.status, body: data})))
.then(obj => console.log(obj));

The .json method returns a promise, not the parsed value itself. If you want to access both the response and the parsed value in the same callback, you'll need to use nested functions like this:
fetch(url)
.then(response => {
response.json().then(parsedValue => {
// code that can access both here
})
});
Alternatively, you can use await inside an asynchronous function to eliminate the need for callbacks.
const response = await fetch(url);
const parsedValue = await response.json();
// code that can access both here
Of course, you'll want to check for errors, either with a .catch(...) call on a Promise or with a try...catch block in an async function. You could make a function that handles JSON and error cases, and then reuse it for all fetches. For example, something like this:
function handle(response) {
if (response.ok) {
return response.json().then(parsedValue => {
// the status was ok and the body could be parsed
return Promise.resolve({ response, parsedValue });
}).catch(err => {
// the status was ok but the body was empty or not JSON
return Promise.resolve({ response });
});
} else {
return response.json().catch(err => {
// the status was not ok and the body was unobtainable/empty/not JSON
throw new Error(response.statusText);
}).then(parsedValue => {
// the status was not ok and the body was JSON
throw new Error(parsedValue.error.message); // assuming an error message is returned by our REST API
});
}
}
I don't think it's the best design pattern, but hopefully this clarifies how the fetch API works.
PS: I avoided naming any variable or property json since that is the name of the text format. Once it's been parsed, it is no longer JSON.

Using two 'then's seem unnecessary to me.
async/await could get the job done pretty easily.
fetch('http://test.com/getData')
.then( async (response) => {
// get json response here
let data = await response.json();
if(data.status === 200){
// Process data here
}else{
// Rest of status codes (400,500,303), can be handled here appropriately
}
})
.catch((err) => {
console.log(err);
})

Did you try this?
return fetch(url)
.then((r)=> {return {response: r.json(), status: r.status}})

I think the cleanest way is to create a Promise.all() with the pieces you need.
.then(response => Promise.all([Promise.resolve(response.ok), response.text()]))
Which can be written shorter as
.then(response => Promise.all([response.ok, response.text()]))
The promise returns an array with all of the results
.then(data => ({ status: data[0], response: data[1] }))

Related

How to store value after running fetch() in javaScript?

I want to update state based on the response of fetch() function but i am unable to get the value instead getting Promise {}.If i console.log() the value is correct there.
state.product = fetch("https://fakestoreapi.com/products/2")
.then((res) => res.json()
.then((json) => {
console.log(json); // Correct value
return json;
})
);
console.log(state.product); // Promise {<pending>}
Fetch return a Promise that resolves to a Response object.
Your second console.log evaluate to a Promise because at this time the Promise has not resolved yet.
Maybe you can try the following :
async function getProduct(id) {
return fetch(`https://fakestoreapi.com/products/${id}`)
.then((res) => res.json())
.then((data) => data);
}
const product = await getProduct(2);
console.log(product);

Can't call fetch api multiple times

I want to call this api multiple times in my project and when I am calling it , It continues giving an error which is
TypeError: Failed to execute 'json' on 'Response': body stream already
read at main.js:Line number
My Code is as Follows
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
a.then((data) => {
return data.json()
}).then((apidata) => {
console.log(apidata)
return apidata
}).catch((error) => {
console.log(error)
})
a.then((fetchdata) => {
return fetchdata.json()
}).then((readingData) => {
console.log(readingData)
}).catch((err) => {
console.log(err)
})
You're not calling fetch multiple times. You're calling it once, and then trying to read the response body multiple times. That's why the error says you're trying to read the body when the stream is already closed — it was closed when you were done reading it the first time.
If you want to use the data twice, store it somewhere and use it twice.
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
a.then((data) => {
return data.json()
}).then((apidata) => {
// **************** Use it twice here
}).catch((error) => {
console.log(error)
})
If you want to fetch it again because it may have been updated, call fetch again:
let thisIsUrl = 'https://api.covid19api.com/summary';
fetch(thisIsUrl)
.then((data) => {
return data.json();
}).then((apidata) => {
console.log(apidata);
return apidata;
}).catch((error) => {
console.log(error);
});
// Presumably this is later (in time), not immediately after the above
fetch(thisIsUrl)
.then((fetchdata) => {
return fetchdata.json();
}).then((readingData) => {
console.log(readingData);
}).catch((err) => {
console.log(err);
});
Finally, this seems unlikely, but if you really want to fetch it once and use that one result in multiple places via the promise chain, keep the promise from then rather than the promise from fetch:
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
.then((data) => {
return data.json()
});
a.then((apidata) => {
// ***** Use it here
}).catch((error) => {
console.log(error)
})
a.then((readingData) => {
// ***** And then again here
}).catch((err) => {
console.log(err);
});
Side note: Your code is falling prey to a footgun in the fetch API; I've written about it in this blog post. fetch only rejects its promise on network errors, not HTTP errors. You have to check for those yourself in the first fulfillment handler, by checking for ok on the response object:
fetch("/your/resource")
.then(response => {
if (!response.ok) {
throw new Error("HTTP error " + response.status); // Or better, use an Error subclass
}
return response.json();
})
// ...
fetch returns Promise, generally, promises have something like state inside themself;
pending: initial state, neither fulfilled nor rejected.
fulfilled: meaning that the operation was completed successfully.
rejected: meaning that the operation failed.
(source)
So when we call them and get the value from them with then, catch and etc. then they change the state after that call. So here, when you read the value with a.then(…, the promise changes its state to fulfilled and you are not able to call it again, you need a new and fresh Promise, actually a new instance of the fetch.
I want to recommend you to use Promise.all().
let thisIsUrl = 'https://api.covid19api.com/summary';
let a = fetch(thisIsUrl)
.then((data) => {
return data.json()
}).then((apidata) => {
console.log(apidata)
return apidata
}).catch((error) => {
console.log(error)
})
Promise.all([a,a,a]);
.then(results => {
// Results are here.
});

get response.status in .then in Java Script

I tried to check if the status of my request is 200 (OK), but I do not know how to do these things together because the first and second .then, are not "like each other":
function f(path) {
await fetch(path)
.then(response => {
// console.log(response.status);
if (response.status != 200) {
throw response.status;
} else {
// do something
}
})
.then(response => response.json())
.then(...method for the response.json()...)
.catch(error => {
// print some error message
}
}
The second then failed and returned error.
I have a problem when I throw that.
It prints to the error to the console (when I check by changing the path to wrong path and I want to see if I treat errors).
what can I do?
You're checking it correctly in your first fulfillment handler (then callback), although I'd just use !response.ok. You don't usually need the status in the subsequent handlers.
But the problem with your first fulfillment handler is that it's not returning anything, so the subsequent fulfillment handler only sees undefined. Instead, return the promise from json():
function f(path) {
fetch(path)
.then(response => {
if (!response.ok) {
// Note: Strongly recommend using Error for exceptions/rejections
throw new Error("HTTP error " + response.status);
}
return response.json();
})
.then(data => {
// ...use the data here...
})
.catch(error => {
// ...show/handle error here...
});
}
Note that you can't use await in a traditional function, only in an async function. But you don't need it if you're using .then and .catch. I've removed it above.
If for some reason you wanted the status in subsequent fulfillment handlers, you'd have to return it from the first fulfillment handler. For instance:
function f(path) {
fetch(path)
.then(response => {
if (!response.ok) {
// Note: Strongly recommend using Error for exceptions/rejections
throw new Error("HTTP error " + response.status);
}
return response.json().then(data => ({status: response.status, data}));
})
.then(({status, data}) => {
// ...use `status` and `data` here...
})
.catch(error => {
// ...show/handle error here...
});
}
In that, I've used a nested fulfillment handler on the promise from json(), and then returned an object with status and data on it.
You need to return in your then chain, which at a glance appears to be one too many. Check out the following example...
fetch(path)
.then(r => r.ok ? r.json() : Promise.reject('oops')) // .statusText, etc
.then(r => {
// [...]
})
.catch(e => console.error(e)); // oops
a) I don't think you need await keyword since you're using .then() chaining.
b) You have to return something from the first then so as to get that in the next .then()
function f(path) {
await fetch(path)
.then(response => {
// console.log(response.status);
if (response.status != 200) {
throw response.status;
} else {
// do something
// After doing what you need return the response
return response
}
})
.then(response => response.json())
.then(...method for the response.json()...)
.catch(error => {
// print some error message
}
}
Actually, it's not clear what your function has to do. But I think your sturggle comes from not fully understanding how promises chain works. For that, I'd recommend familiarizing yourself with this article, it helped me a lot :)
So back to your function. The elegant solution is adding simple "tap" function, that allows you to do some stuff with current response, but still it passes response further for other .then chains.
Here's the tap function:
const tap = (callback) => (value) => (callback(value), value);
And finally how you can use it:
function f(path) {
fetch(path)
.then(
tap((response) => {
if (response.status !== 200) throw new Error(response.status);
})
)
.then((response) => {
// do other stuff
})
.catch((error) => console.error(error));
}
The number of browsers that support fetch but don't support async/await is now very small, so you may be better off using this simpler syntax first, and then transpiling for legacy browsers along with your shims for fetch.
Your function becomes:
try {
const response = await fetch(path);
// console.log(response.status);
if (response.status != 200) {
throw response.status;
} else {
// do something
}
const parsed = await response.json();
// do something with parsed
}
catch(error) {
// print some error message
}
This new syntax makes it much easier to deal with errors in the different then actions:
const response = await fetch(path);
// console.log(response.status);
if (response.status != 200) {
throw response.status;
} else {
// do something
}
let parsed; // Will hold the parsed JSON
try {
parsed = await response.json();
}
catch(error) {
// Deal with parsing errors
}
try {
// do something with parsed
}
catch(error) {
// Deal with errors using the parsed result
}

Extracting array elements from JSON (javascript)

I'm trying to manipulate JSON data received from an API url (this is my first time handling this type of work)
The following function returns a promise of a 20 element array:
const articles = () => {
return fetch(url)
.then(res => res.json())
.then(post => post.articles);
};
Console view:
Now, I'd like to extract the elements from the array - I tried something like:
articles()[0].name
but this doesn't work and I'm not sure of an alternative way to go about this? Appreciate your help. Thanks
Your articles fucntion returns a promise. You have to consume the promise (more on MDN):
articles().then(articleArray => {
console.log(articleArray);
});
or within an async function:
const articleArray = await articles();
console.log(articleArray);
Side note: Your fetch code is missing a check for HTTP success (HTTP failure isn't a rejection). You're by far not the only person who misses out this check, so much so that I've written a post on my anemic blog about it. With the check:
const articles = () => {
return fetch(url)
.then(res => {
if (!res.ok) {
throw new Error("HTTP error " + res.status);
}
return res.json();
})
.then(post => post.articles);
};

Reading the body returned for fetch() response which contains an HTTP error code [duplicate]

I have an HTTP API that returns JSON data both on success and on failure.
An example failure would look like this:
~ ◆ http get http://localhost:5000/api/isbn/2266202022
HTTP/1.1 400 BAD REQUEST
Content-Length: 171
Content-Type: application/json
Server: TornadoServer/4.0
{
"message": "There was an issue with at least some of the supplied values.",
"payload": {
"isbn": "Could not find match for ISBN."
},
"type": "validation"
}
What I want to achieve in my JavaScript code is something like this:
fetch(url)
.then((resp) => {
if (resp.status >= 200 && resp.status < 300) {
return resp.json();
} else {
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
}
})
.catch((error) => {
// Do something with the error object
}
// This does not work, since the Promise returned by `json()` is never fulfilled
return Promise.reject(resp.json());
Well, the resp.json promise will be fulfilled, only Promise.reject doesn't wait for it and immediately rejects with a promise.
I'll assume that you rather want to do the following:
fetch(url).then((resp) => {
let json = resp.json(); // there's always a body
if (resp.status >= 200 && resp.status < 300) {
return json;
} else {
return json.then(Promise.reject.bind(Promise));
}
})
(or, written explicitly)
return json.then(err => {throw err;});
Here's a somewhat cleaner approach that relies on response.ok and makes use of the underlying JSON data instead of the Promise returned by .json().
function myFetchWrapper(url) {
return fetch(url).then(response => {
return response.json().then(json => {
return response.ok ? json : Promise.reject(json);
});
});
}
// This should trigger the .then() with the JSON response,
// since the response is an HTTP 200.
myFetchWrapper('http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY').then(console.log.bind(console));
// This should trigger the .catch() with the JSON response,
// since the response is an HTTP 400.
myFetchWrapper('https://content.googleapis.com/youtube/v3/search').catch(console.warn.bind(console));
The solution above from Jeff Posnick is my favourite way of doing it, but the nesting is pretty ugly.
With the newer async/await syntax we can do it in a more synchronous looking way, without the ugly nesting that can quickly become confusing.
async function myFetchWrapper(url) {
const response = await fetch(url);
const json = await response.json();
return response.ok ? json : Promise.reject(json);
}
This works because, an async function always returns a promise and once we have the JSON we can then decide how to return it based on the response status (using response.ok).
You would error handle the same way as you would in Jeff's answer, however you could also use try/catch, an error handling higher order function, or with some modification to prevent the promise rejecting you can use my favourite technique that ensures error handling is enforced as part of the developer experience.
const url = 'http://api.openweathermap.org/data/2.5/weather?q=Brooklyn,NY'
// Example with Promises
myFetchWrapper(url)
.then((res) => ...)
.catch((err) => ...);
// Example with try/catch (presuming wrapped in an async function)
try {
const data = await myFetchWrapper(url);
...
} catch (err) {
throw new Error(err.message);
}
Also worth reading MDN - Checking that the fetch was successful for why we have to do this, essentially a fetch request only rejects with network errors, getting a 404 is not a network error.
I found my solution at MDN:
function fetchAndDecode(url) {
return fetch(url).then(response => {
if(!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
} else {
return response.blob();
}
})
}
let coffee = fetchAndDecode('coffee.jpg');
let tea = fetchAndDecode('tea.jpg');
Promise.any([coffee, tea]).then(value => {
let objectURL = URL.createObjectURL(value);
let image = document.createElement('img');
image.src = objectURL;
document.body.appendChild(image);
})
.catch(e => {
console.log(e.message);
});
Maybe this option can be valid
new Promise((resolve, reject) => {
fetch(url)
.then(async (response) => {
const data = await response.json();
return { statusCode: response.status, body: data };
})
.then((response) => {
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(response.body);
} else {
reject(response.body);
}
})
});

Categories