Undefined data after storing API response to cache - javascript

I am building a simple JS application that fetches data from an API. It's working fine but now that I'm trying to implement the cache API I'm running into some issues.
My fetchData function:
function fetchUsers(){
fetch(request)
.then((response) => {
let resClone = response.clone();
response.json();
cache.storeResponse("requestsCache", request, resClone); // cache data
})
.then((data) => {
users.push(...data.users);
this.displayResults(users);
});
}
And the cache functions I'm using:
const cache = {
canUseCache: function () {
return "caches" in window;
},
getCachedResponse: async function (cacheName, request) {
if (!this.canUseCache) {
return false;
}
const openCache = await caches.open(cacheName);
const cachedResponse = await openCache.match(request);
if (!cachedResponse || !cachedResponse.ok) {
return false;
}
let cachedData = await cachedResponse.json();
return cachedData;
},
storeResponse: async function (cacheName, request, response) {
if (!this.canUseCache) {
return false;
}
const opencache = await caches.open(cacheName);
opencache.put(request, response);
},
};
After adding the cache.storeResponse line now my data is undefined. I haven't used cache before so any help would be much appreaciated.

Related

How can I "encapsulate" this code into a module so it could become reusable?

I have got this Node.JS snippet and would like to write it as a module, so I can use recaptcha in different parts of my system.
This is how it currently looks like:
app.post('/register_user', (req, res) => {
const secret_key = process.env.RECAPTCHA_SECRET;
const token = req.body.recaptcha;
const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${token}`;
fetch(url, { method: "post",})
.then((response) => response.json())
.then((google_response) => {
if (google_response.success == true) {
res.format({'text/html': () => res.redirect(303, '/register'),})
} else {
return res.send({ response: "Failed" });
}
})
.catch((error) => {
return res.json({ error });
});
})
I have tried to write the following module which works absolutely great, but I have absolute no idea about how to call it from the app.post, since I always get undefined as return:
import fetch from 'node-fetch';
export function fetch_out(url, timeout = 7000) {
return Promise.race([
fetch(url),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('timeout')), timeout)
)
]);
}
export async function checkRecaptcha(token, secret_key){
const url = "https://www.google.com/recaptcha/api/siteverify?secret=" + secret_key + "&response=" + token;
try{
const response = await fetch_out(url, 1000);
const google_response = await response.json();
}catch(error){
return error;
}
return google_response;
}
Any help would be appreciated! Thanks!
You could make this method reusable by removing the framework actions that need to happen and only return if the validation was successful or not. This way, it will be reusable in another project that doesn't use a specific framework.
Example module;
export async function checkRecaptcha(token, secret_key) {
const url = `https://www.google.com/recaptcha/api/siteverify?secret=${secret_key}&response=${token}`;
const response = await fetch(url, { method: "post",});
if (!response.ok) return false;
const json = await response.json();
if (!json.success) return false;
return true;
}
Usage:
import { checkRecaptcha } from "./some-file-name";
app.post('/register_user', async (req, res) => {
const isHuman = await checkRecaptcha(req.body.recaptcha, process.env.RECAPTCHA_SECRET);
if (!isHuman) {
return res.send({ response: "Failed" });
}
return res.format({'text/html': () => res.redirect(303, '/register'),});
});
If you specifically want to call an action after the validation, you can also use successful and error callbacks.

How should I return data

I'm trying to understand and implement simple app. I've got two express servers, first is only sending plain object and the second is fetching it.
Inside the second app I want to build a class - Loader, that will provide fetch and get (data) methods.
class Loader{
constructor(){
this.data = {data:"some data"}
}
async fetchData(){
const res = await axios.get("http://localhost:5000/data");
if(res) {
this.data = res.data
console.log(this.data)
return res.data
}
}
getData(){
return this.data
}
async getFetchedData(){
await this.fetchData();
console.log(this.data)
this.getData();
}
}
But actually it doesn't work the way I wanted it. I'd like to implement method that will return fetched object.
const testLoader = new Loader();
testLoader.getFetchedData();
const testVar = testLoader.getData();
console.log("test", testVar)
The output is:
test { data: 'some data' }
data from fetchData { data: 'test data after fetching' }
data from getFetchedData { data: 'test data after fetching' }
And I understand - getData is sync and the rest methods are async, so that's the order of perform. But how do I need to rewrite code to receive method that will return the fetched object?
You'll need to await for any async methods to complete (or then-chain them) wherever you use them.
const testLoader = new Loader();
await testLoader.fetchData();
const testVar = testLoader.getData();
console.log("test", testVar)
you can try some code i made
class Loader{
constructor(){
this.data = {data:"some data"}
}
async fetchData(){
const res = await axios.get("http://localhost:5000/data");
this.data = res.data
}
getData(){
return this.data
}
async getFetchedData(){
await this.fetchData();
console.log(this.data)
return this.getData();
}
}
// 1
const exec = async () => {
try {
const loader = new Loader()
await loader.fetchData()
console.log(loader.getData())
} catch(e) {
console.log("ERROR", e)
}
}
exec()
// 2
const exec = async () => {
try {
const loader = new Loader()
const data = await loader.getFetchedData()
console.log(data)
} catch(e) {
console.log("ERROR", e)
}
}
exec()
// 3
const loader = new Loader()
loader.fetchData().then(() => {
console.log(loader.getData())
}).catch((e) => {
console.log("ERROR", e)
})
// 4
const loader = new Loader()
loader.getFetchedData().then((data) => {
console.log(data)
}).catch((e) => {
console.log("ERROR", e)
})

response.json is undefined express.js

So here's my code for now:
function geoPerformAction(e) {
getGeoApiData(document.getElementById('zipPostCode').value)
.then((APIarr) => {
postGeoData('/geoadd', { Lat: APIarr[0] });
})
.then(function () {
updateUIGeo();
})
}
//Friend helped with me with get API data
/* Function to GET Web API Data*/
const getGeoApiData = async ( place) => {
const response = await fetch("https://pokeapi.co/api/v2/pokemon/" + place + "/");
try {
const webData = response.json();
const Pla = webData;
console.log(Pla);
const APIarr = [Pla];
return APIarr;
}
catch (error) {
console.log("error", error);
}
}
Everytime I use this, the webdata variable is undefined. Why is this happening? Why isn't returning the data that I requested for?
Thank you.
You are not awaiting for the second promise to be resolved
const webData = await response.json();
Example:
async function fetchAsync () {
// await response of fetch call
const response = await fetch('https://api.github.com');
// only proceed once promise is resolved
const data = await response.json();
// only proceed once second promise is resolved
return data;
}

Save fetched JSON data to sessionStorage

I just figured out how to write an async/await function to fetch data from an API, and it's working, but it's hitting the API like crazy. So now I'm trying to save the fetched data to sessionStorage and only fetch from the API if the data isn't in the sessionStorage.
Here's my code:
const fetchMeetingData = async () => {
console.log('api hit')
try {
const response = await fetch(https://sheet.best...)
const data = await response.json()
validate(data) // Clean data to remove null key values
return data
} catch (e) {
console.log('Fetch error with getMeetingData()')
}
}
const filterMeetings = async (filters) => {
meetings = await fetchMeetingData()
meetings.forEach((meeting) => {
meeting.time2 = moment(meeting.time, ["h:mm A"]).format("HHmm")
})
let today = moment().format("dddd").toString()
let hour = moment().format('HHmm').toString()
let filteredMeetings = meetings.filter(function (matches) {
if (document.querySelector('#select-day').selectedIndex === 0 && filters.searchText === '') {
return matches.day === today &&
moment(matches.time, ["h:mm A"]).format("HHmm") > hour
} else {
return true
}
})
Here's what I've tried:
const fetchMeetingData = async () => {
console.log('api hit')
try {
const response = await fetch(https://sheet.best...)
const data = await response.json()
validate(data) // Clean data to remove null key values
sessionStorage.setItem('meetingData', JSON.stringify(data)) // added this line
return data
} catch (e) {
console.log('Whoa! Fetch error with getMeetingData()')
}
}
I'm not really sure where to go from here, or if this is even the correct approach. My noob instinct was to do something like this, which didn't work.
savedMeetingData = sessionStorage.getItem('meetingData')
const getSavedMeetingData = async () => {
if (savedMeetingData) {
meetings = savedMeetingData
return meetings
} else {
fetchMeetingData()
meetings = await data
return meetings
}
const filterMeetings = async (filters) => {
meetings = await getSavedMeetingData() // replaces call to fetchMeetingData
meetings.forEach((meeting) => {
meeting.time2 = moment(meeting.time, ["h:mm A"]).format("HHmm")
})
I'm not sure if that's exactly the code I was trying but it's close. The problem was the API was still getting hit, even though the data was stored successfully to sessionStorage.
I'd really appreciate some help and/or suggestions on how to clarify this question.
SOLUTION:
Based on answer from #Christian
// StackOverflow Q/A
async function getMeetingData() {
const preLoadedData = sessionStorage.getItem('meetingData')
if(!preLoadedData) {
try {
const response = await fetch('https://sheet.best...')
const data = await response.json()
validate(data)
sessionStorage.setItem('meetingData', JSON.stringify(data))
console.log('api hit')
return data
} catch (e) {
console.log('Whoa! Fetch error with getMeetingData()')
}
} else {
console.log('no api hit!!!')
return JSON.parse(preLoadedData)
}
}
async function getSavedMeetingData() {
const meetings = await getMeetingData()
return meetings
}
const filterMeetings = async (filters) => {
meetings = await getSavedMeetingData()
meetings.forEach((meeting) => {
meeting.time2 = moment(meeting.time, ["h:mm A"]).format("HHmm")
})
If you could be more explicit on what exactly did not work it would be great :) (did not save data in sessionStorage?, could not retrieve it?, etc...). Anyway, maybe you could try something like this and see if it helps:
async function getSavedMeetingData() {
const meetingData = await getMeetingData();
}
async function getMeetingData() {
const preloadedData = sessionStorage.getItem('meetingData');
if (!preloadedData) {
try {
const response = await fetch('https://myapiurl.com/');
const data = validate(response.json());
sessionStorage.setItem('meetingData', JSON.stringify(data));
return data;
} catch (e) {
console.log('Whoa! Fetch error with getMeetingData()');
}
} else {
return JSON.parse(preloadedData);
}
}
One more reminder (just in case), keep in mind you are saving this to sessionStorage, so if you close the tab do not expect to have the information saved, in that case you should use localStorage.

Using promises in Axios requests

I am trying to work out the best way to achieve something. When I land on a Profile page, the Profile component loads the data for that profile. This is assigned to this.profile. Within this data is a path to a file, where I want to process some data using this file. To me, the below approach seems slightly risky.
created() {
let vm = this;
let url = `/api/profile/${this.$route.params.id}`;
axios.get(url).then(response => {
this.profile = response.data;
d3.json(response.data.fileName)
.then(function (data) {
//do some stuff
}).catch(function (error) {
// handle error
});
});
}
Instead of that, I want to ensure that I first have the data from the axios call. So I am thinking I need a promise? I was thinking something more along the lines off
created() {
let vm = this;
let url = `/api/profile/${this.$route.params.id}`;
axios.get(url).then(response => {
this.profile = response.data;
}).then() {
d3.json(response.data.fileName)
.then(function (data) {
//do some stuff
}).catch(function (error) {
// handle error
});
};
}
But the above is incorrect, it is mainly to show what I am trying to achieve. I was wondering how I can maybe use deferred and promises to only execute the d3 stuff once the axios call is made.
Thanks
You can solve this by chaining promises, assuming that d3.json returns a promise:
created() {
let vm = this;
let url = `/api/profile/${this.$route.params.id}`;
axios.get(url)
.then(response => {
this.profile = response.data
return d3.json(response.data.fileName)
}).then(data => {
//do some stuff
}).catch(err => {
//log error
})
}
That's where async/await comes in handy. A you don't need to save this to a variable and B you have cleaner, more readable code.
async created() {
const url = `/api/profile/${this.$route.params.id}`;
const { data } = await axios.get(url); // Optional destructuring for less clutter
this.profile = data;
const d3Data = await d3.json(data.fileName);
//do whatever you want
}
async created() {
let vm = this;
let url = `/api/profile/${this.$route.params.id}`;
try {
const {data} = await axios.get(url)
const d3Data = await d3.json(data.fileName)
} catch(err) {
//error
}
}

Categories