Reread a response body from JavaScript's fetch - javascript

fetch() returns promise which (if successful) resolves to a Response object. A very common thing to do is immediately call Response.json() to convert the response body to a JSON object.
If the response body isn't valid JSON, then the Response.json() promise fails with an error. The message is something along the lines of:
Unexpected token X in JSON at position 0
That's not very helpful when trying to diagnose the problem; ideally I'd like to be able to see the content from the server (which is often an error message).
However, it appears that you can only read the stream at Response.body once (at least in Chrome). (There's even a read-only Response.bodyUsed flag.) That has already happened when Response.json() tries to convert the body to JSON, so the body appears to be lost forever in the event of a JSON parsing failure.
Is there any way to recover the original response body... short of manually reading it (and then converting to JSON) when the original fetch Promise resolves?

Use Response.clone() to clone Response
let clone = response.clone();
Alternatively, use Response.body.getReader() which returns a ReadableStream to read Response as a stream, TextDecoder() to convert Uint8Array data stream to text.

I had to deal with an API that occasionally botched the JSON response - before returning response.json() I made a clone of the response object. using a catch block, I can determine if the error is a SyntaxError, and proceed to fix the error using the text result of the response clone
a little like this:
var brokenJson = function (url) {
var responseCopy;
return fetch(url)
.then(function (response) {
responseCopy = response.clone();
return response.json();
}).catch(function (err) {
if (err instanceof SyntaxError) {
return responseCopy.text()
.then(function(data) {
return fixJson(data);
});
}
else {
throw err;
}
}).then(function (json) {
// do things
});
};
fixJson is just a function that fixes the received data - in my case, when it was broken JSON, it was always broken the same way - I think it had an extra leading { or trailing } - can't recall
re-reading the question, you're more likely to want to log the error to the console rather than fix the json - easy rewrite:
var brokenJson = function (url) {
var responseCopy;
return fetch(url)
.then(function (response) {
responseCopy = response.clone();
return response.json();
}).catch(function (err) {
if (err instanceof SyntaxError) {
return responseCopy.text()
.then(function(text) {
console.error(text);
throw err;
});
}
else {
throw err;
}
}).then(function (json) {
// do things
});
};

Assigning the response.json() to a variable and returning it worked for me. clone() was again saying its locked.
fetch("http://localhost:3000/watchlist")
.then(response => {
var res = response.json();
return res;
})
.then(data => {
console.log(data);
this.setState({ data });
});

I used JSON.parse(response.resp.data) as somehow clone didn't work.

Related

How do I access the response value for a fetch/http call using Promise.allSettled?

I'm trying to use Promise.allSettled to call 3 urls and process the results when all are finished. However, I cannot seem to access the value of the responses, which are JSON. Here is my code:
let urlList = ['http...', 'http...', 'http...'];
let fetches = urlList.map(url => fetch(url));
Promise.allSettled(fetches)
.then((results) => { // (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
//result.value is a response object and I cannot seem to access the response.body or the actual text/json value of the response
// in chrome developer tools, I can see the results I want, just can't seem to read them here
console.log(result.value);
}
else if (result.status == "rejected") {
console.log(`Failed Calling: ${urls[num]}\n\treason: ${result.reason}`);
}
});
//call other method with combined results...
})
.catch((err) => {
alert(err);
});
console.log output:
What am I missing here? Thanks in advance for the help
Change this:
let fetches = urlList.map(url => fetch(url));
to this:
let fetches = urlList.map(url => fetch(url).then(res => res.json()));
That way your Promise.allSettled() will give you an array of promises that resolve to your final value, not just to the response headers object so your result.value property will be the final value for each promise.

Embedding multiple videos using oEmbed and Javascript

I'm trying to embed multiple videos to a web page using Vimeo's oEmbed. The idea is to simply enter the url in the CMS which will generate a div for each item containing the code below.
This javascript is doing what I want but only works with the first item. When I check the console there's only one response which contains the JSON metadata for the first item/video.
Probably this is not the best method but is getting the job done, all I need is to make it work for multiple items. Any ideas how can I do that?
Thank you
<div class="vimeo-video" id="[[+ID]]-video"></div>
<div class="vimeo-info" id="[[+ID]]-info"></div>
<script>
const getJSON = async url => {
try {
const response = await fetch(url);
if (!response.ok) // check if response worked (no 404 errors etc...)
throw new Error(response.statusText);
const data = await response.json(); // get JSON from the response
return data; // returns a promise, which resolves to this data value
} catch (error) {
return error;
}
}
console.log("Fetching data...");
getJSON("https://vimeo.com/api/oembed.json?url=[[+myVideoURL]]").then(data => {
document.getElementById("[[+ID]]-video").innerHTML = data.html;
document.getElementById("[[+ID]]-info").innerHTML = '<h2>' + data.title + '</h2>' + data.description;
console.log(data);
}).catch(error => {
console.error(error);
});
</script>
In case somebody with basic javascript skills like me goes through something similar. The problem was a rookie's mistake, I had to use var instead of const.
The reason is because var variables can be updated and re-declared but const variables can neither be updated nor re-declared. So here's the working code:
var getJSON = async (url) => {
try {
var response = await fetch(url);
if (!response.ok)
// check if response worked (no 404 errors etc...)
throw new Error(response.statusText);
var data = await response.json(); // get JSON from the response
return data; // returns a promise, which resolves to this data value
} catch (error) {
return error;
}
};

Async Promise returns undefined or zone aware promise

When calling a function that returns a promise, comes back as undefined unless async operators are removed, then returns ZoneAwarePromise, but contains no data.
I know the query returns data when the function executes, it however does not seem to pass that data to the actual return part of the function call.
I have looked at several Stack questions that have not answered this question including this question:
Async/Await with Request-Promise returns Undefined
This is using a REST endpoint to pull data, the console.logs do show the data is correct, however return comes back as undefined
this.allPeople.forEach(async person => {
const dodString = await this.getRelatedRecords(person); //undefined
}
This is the main function that returns a promise / data
async getRelatedRecords(person) {
// function truncated for clarity
// ...
//
console.warn('This async should fire first');
selPeopleTable.relationships.forEach(relationship => {
allRelationshipQueries.push(
arcgisService.getRelatedTableData(
selPeopleTable.url, [person[oidField.name]], relationship.id, relationship.name),
);
});
await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
}).catch(function(data){
console.log('there might be data missing', data);
});
}
Removing the ASYNC operators cause the getRelatedRecords() to fire after the containing function and / or return a 'ZoneAwarePromise' which contains no data. I need getRelatedRecords() to fire first, then to run the rest of the code.
I can provide more snippets if need be.
Zone Aware Promise
When the Async operators are (I think) setup correctly
You need to return this as well:
await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
})
return in the above block is returning but all of this is in the scope of the arrow function which is then(allResults => { so you also need to return this function like this:
return await Promise.all(allRelationshipQueries).then(allResults => {
Approach #2:
Second way would be to store that into variable like this:
let dataToReturn = await Promise.all(allRelationshipQueries).then(allResults => {
console.log('Inside the Promise');
// The Specific node I am looking for
const data = allResults[1].results.relatedRecordGroups[0].relatedRecords[0].attributes.dod;
console.log(data); // Shows correctly as the data I am looking for
return data;
}).catch(function(data){
console.log('there might be data missing', data);
});
return dataToReturn;

Setting value of variable within promise chain in Javascript

In my web application, there are several components that will need access to the same data (in JSON). To avoid making unnecessary REST calls, I made a module that is supposed make a fetch request and store the result in a variable. On subsequent requests... if the data is available in the module, it will be directly returned (so only one network request is necessary).
For example:
var data_module = function(){
var data; //Module stores the json data in a variable
return{ //Returns an object that contains a public method accessible to external functions
get_json:function(){
if(data){ //If data already exists, then return a Promise object that immediately resolves with data
return Promise.resolve(data);
}
else{ //Else if data does not exist, make fetch request
fetch('/rest/url/endpoint', {credentials:'include'})
.then(function(response){
if(!response.ok){
throw new Error(response.statusText);
}
return response.json(); //Returns json of response
})
.then(function(json){
data = json; //Assigns data the value of json to store the result for subsequent requests
return Promise.resolve(data) //Returns a Promise that resolves with data
});
}
} //Public method that is supposed to provide access to data
}
}(); //Module will automatically execute
Outside of the module, I will try to access data like so:
some_dom_element.onclick = function(){ //Some html element is clicked and we need the data
data_module.get_json().then(function(json){
console.log(json); //However this never gets called
});
}
It does not work. Even though data_module's get_json function returns a Promise, the .then method does not get called outside of data_module. I was wondering if anyone can explain why this happens? (Or provide a general direction of how to modify the solution to achieve the goal of storing json results of fetch requests).
Thanks in advance!
You need to return the fetch
var data_module = (function() {
var data; //Module stores the json data in a variable
return { //Returns an object that contains a public method accessible to external functions
get_json: function() {
if (data) { //If data already exists, then return a Promise object that immediately resolves with data
console.log("** From CACHE **")
return Promise.resolve(data);
} else { //Else if data does not exist, make fetch request
// returning the fetch
return fetch('https://jsonplaceholder.typicode.com/posts/1', {
credentials: 'include'
})
.then(function(response) {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json(); //Returns json of response
})
.then(function(json) {
data = json;
return Promise.resolve(data) //Returns a Promise that resolves with data
});
}
} //Public method that is supposed to provide access to data
}
}()); //Module will automatically execute
//Outside of the module, I will try to access data like so:
var some_dom_element = document.getElementById("testBt")
some_dom_element.onclick = function() { //Some html element is clicked and we need the data
data_module.get_json().then(function(json) {
console.log(json); //However this never gets called
});
}
<button id="testBt">Test</button>
You could actually make the code a little shorter by storing the promise that response.json() returns and just returning it rather than storing the text and generating a new Promise every time you get it from the cache.
let data_module = (function() {
let data;
return {
get_json: () => {
if (!data) {
data = fetch('https://jsonplaceholder.typicode.com/posts/1', {
credentials: 'include'
})
.then(response => {
if (!response.ok) {
// clear data so next time it tries to contact the server again
data = undefined;
throw new Error(`${response.status}: ${response.statusText}`);
} else {
// return the promise that .json() returns so it is stored
// in data
return response.json();
}
});
}
return data;
}
};
}());
data_module.get_json().then(data => {
console.log(data);
});
data_module.get_json().then(data => {
console.log(data);
});
I used let and arrow functions => since any browser that supports fetch also supports those.

Did I set my map variable properly to a fetched json?

I have some code below to transform a fetched json into string and remove the braces and parse it into a map variable:
let result = '';
let map = [];
fetch(link)
.then(function(response) {
return response.json();
}).then(function(json) {
result = JSON.stringify(json); //json here is like [{'a':1,'a2':2},{'b':11,'b2':12}]
result = result.substring(1, result.length - 1);
map = JSON.parse(result);
}).catch(function(ex) {
console.log('parsing failed', ex);
});
I've tried to simply set map=json but it both gives out the same error that I have duplicate keys for the map.
If I hard code my map variable to lets say [{'id':1,code: 007},{'id':2, code:014}] it works. I've tried to log result after changing it to a string and its exactly as the example. So in other words, setting map=json should work in the first place.
I think I might be missing something. What is it?
EDIT #1:
fetch(link)
.then(function(response) {
return response.json();
}).then(function(json) {
setData(json);
document.getElementById("result").innerHTML = json;
}).catch(function(ex) {
console.log('parsing failed', ex);
});
function setData(json) {
map = json;
}
I've tried the solution given by Naomik except without the response ok part.
I'm still receiving the same error as map = json. Any help with this?
You can't set a variable like that in an asynchronous handler and expect it to be set outside of the handler. More about that here: "How do I return the response from an asynchronous call?"
I would've just marked your question as a duplicate of that one, but there's other issues with your code so I'm gonna address those now
In your second .then call, you are attempting to process json but you're doing it incorrectly
// Why exactly are you re-stringifying the data we just parsed ?
result = JSON.stringify(json);
// ... wtf ?
result = result.substring(1, result.length - 1);
// Same as above, any vars you set here cannot be read outside of the callback.
// This won't work like you expect (see the related question I linked above)
map = JSON.parse(result);
Remember, JSON is just a string that represents your data, nothing else
JSON.parse will take a json string and turn it into data.
JSON.stringify will take data and turn it into a json string.
This example might help you a bit
fetch(link).then(function(response) {
if (response.ok)
// read JSON response and parse it
return response.json()
else
// response is not OK, throw error
throw Error(response.status + " " + response.statusText)
})
.then(function(data) {
console.log("Your data has arrived!")
console.log("Do something here with your data")
doSomething(data)
})
.catch(function(err) {
console.error(err.message)
})
function doSomething(data) {
console.log("Here's your data:", data);
}

Categories