Prevent same call on an api response - javascript

I'm learning to use calls on APIs and I'm trying to get my API response to not return the same data over and over again, currently, it does despite using setInterval, I have tried changing the order and making it async but am currently not able to figure out how to change this.
The idea is to make an original call to the API on page load, then after 6 or so seconds, make the call again but change the response automatically hence the setInterval.
Here is my code:
const advice = document.getElementById("advice");
const adviceNum = document.getElementById("adviceNum");
const slip_id = Math.random() * 100;
console.log(slip_id)
fetch(`https://api.adviceslip.com/advice/${slip_id}`)
.then(response => {
return response.json();
})
.catch(error => {
console.log(error);
})
.then(data => {
console.log(data);
const returnedAdvice = data.slip.advice;
console.log(returnedAdvice);
const idAdvice = data.slip.id;
adviceNum.innerText = `ADVICE #${idAdvice}`;
advice.innerText = `"${returnedAdvice}"`;
setInterval(() => {
console.log(data);
const returnedAdvice = data.slip.advice;
console.log(returnedAdvice);
const idAdvice = data.slip.id;
adviceNum.innerText = `ADVICE #${idAdvice}`;
advice.innerText = `"${returnedAdvice}"`;
}, 8000)
})
Would appreciate any help on what I'm doing wrong here!

Currently you're making a fetch request and when the data returns from that single request; you console.log etc.
Instead try the following, wrap your fetch request in the setInterval
setInterval(async function(){
const data = await fetch(`https://api.adviceslip.com/advice/${slip_id}`);
// check your response
// compute your values
// add them to the dom
}, 8000);

Related

Event listener for foreach loop completion

so what I have right now is
window.addEventListener('load', fetchInfo)
function fetchInfo() {
const tableRows = //an array of results
tableRows.forEach((row) => {
const rowId = //get the id of each row
fetch(...) //fetch some stuff using the id
.then(() => {
//do some stuff
return rowId;
})
.then((id) => {
//do some stuff
}
})
})
}
basically using rowId to fetch information and populate each table row, so this happens a few times, the table maxes out at 10 rows so max 10 fetches
I want to have an event listener to see when all the fetching is done, aka when the table is completely done loading. How should I go about that?
Edit: these fetches are api requests so they take a few seconds to respond. I've tried using Promise.all(tableRows.map(row) => and it returned results before the api could respond. So in the end, it still doesn't really detect when does the table actually finish loading information.
Use Promise.all:
async function fetchInfo() {
const tableRows = //an array of results
const results = await Promise.all(tableRows.map(row => fetch(...))
console.log(results)
}
From the suggestion in the comments length was indeed the way, however that would only be half of the solution. Because the key point here is that I want to know whether or not the table has been fully populated with requested information.
Tried Promise.all and map() and didn't work out because as stated in my edit, these will return when the fetch was called, when the fetch was still pending, and doesn't really care if the fetch was a 200 OK, which was what I needed.
So the solution was to use response.status
function fetchInfo() {
const tableRows = ...
let successFetch = 0;
tableRows.forEach((row) => {
fetch(...)
.then((response) => {
if (response.status == 200) {
successFetch = successFetch + 1
}
if (successFetch == tableRows.length) {
//this point here was exactly what I needed
}
return response;
})
.then((response) => {
...
})
})
}
Use Promise.all to wait for all promises to complete
Promise.all(
tableRows.map((row) => {
return fetch(...).then(() => {
return rowId;
})
})
).then((results) => {
console.log(results);
})

How do I make the output of this Javascript API response global?

I'm trying to receive the output for the following code where the cc variable would log a value into the empty global country variable. Afterwards print it to the console, however it isn't working. How would I make the local variable cc here to global/give the global variable country a value?
var country = '';
fetch('https://extreme-ip-lookup.com/json/')
.then( res => res.json())
.then(response => {
var cc = (response.countryCode);
country = cc;
});
console.log(country);
It seems like your problem has to do with the asynchronous nature of your code. Let me explain.
var country = '';
fetch('https://extreme-ip-lookup.com/json/')
.then( res => res.json())
.then(response => {
var cc = (response.countryCode);
country = cc;
});
console.log(country);
The fetch function is asynchronous. That's why you need the .then methods. This means that while the fetch function runs, JavaScript doesn't stop the rest of the program, and instead moves on while fetch() runs in the background. Hence, when you console.log(country), it's still of the original value (empty string).
To answer your question, you can use Promises to log the value of cc.
var country = '';
const fetchPromise = fetch('https://extreme-ip-lookup.com/json/')
.then( res => res.json())
.then(response => {
var cc = (response.countryCode);
country = cc;
});
Promise.resolve(fetchPromise) // Waits for fetchPromise to get its value
.then(() => console.log(country))
You can find out more about promises at the MDN docs
The problem with your currently call console.log(country) before country is set to response.countryCode.
You could solve this by placing your code inside an async IIFE in the following manner:
(async () => {
const response = await fetch('https://extreme-ip-lookup.com/json/');
const ipData = await response.json();
const country = ipData.countryCode;
// place all code that uses `country` in here
console.log(country);
})();
If you have another script with function definitions that depends on county be sure to accept it as parameter and don't pull the data from a global variable.
// helper_functions.js
// Bad
function someFunctionThatUsesCountry() {
console.log(country); // <- don't expect the global to be set
}
// Good
function someFunctionThatUsesCountry(country) {
console.log(country); // pull country ^ from the parameter list
}
You can then call you other script inside the IIFE by just passing the value.
(async () => {
// ...
someFunctionThatUsesCountry(country);
})();
If for some reason want a global variable really bad. You should place the promise inside this variable, not the value. With this promise you can pass the value, as well as notify other scripts when this value is available.
// script_1.js
window.country = fetch('https://extreme-ip-lookup.com/json/')
.then(response => response.json())
.then(ipData => ipData.countryCode);
// script_2.js (must be loaded after script_1.js)
window.country.then(country => { // <- wait until country is available
// do stuff with country
console.log(country);
});
Your problem come from the fact that your fetch is an asynchrone function, with a promise.
What you want to do is that (I suppose)
var country = '';
//then
fetch('https://extreme-ip-lookup.com/json/')
.then( res => res.json())
.then(response => {
var cc = (response.countryCode);
country = cc;
});
//then
console.log(country);
But, since you use an async function, this is what is done:
//first
var country = '';
//second
fetch('https://extreme-ip-lookup.com/json/')
.then( res => res.json())
.then(response => {
//fourth
var cc = (response.countryCode);
country = cc;
});
//third
console.log(country);
How to fix that? it depend. If your console.log is triggered by a button, make it wait for country to be filled
else, put your code in the last then, or use Promise.all() (documentation here)
console.log is happening before fetch is resolved. Try this:
let country = '';
fetch('https://extreme-ip-lookup.com/json/')
.then(res => res.json())
.then(res => country = res.countryCode)
.then(() => console.log(country))
fetch returns a Promise, hence the .then() chain in your question.
But if you stepped into Promise Land, you won't find out. You'll have to do everything with Promises.
But you might split you logic into small manageable parts like here
console.clear()
let cached = null;
const lookup = () => fetch('https://extreme-ip-lookup.com/json/');
// Caching json and not the fetch promise, because
// https://stackoverflow.com/a/54731385/476951
const getJson = () => cached = cached || lookup() .then(response => response.json());
const getParameter = (parameter) => getJson() .then(json => json[parameter]);
const getCountry = () => getParameter('country') .then(value => value);
const getCountryCode = () => getParameter('countryCode') .then(value => value);
getCountryCode().then(countryCode => {
console.log('countryCode:', countryCode)
})
getCountry().then(country => {
console.log('country:', country)
})
getParameter('city').then(city => {
console.log('city:', city)
})

Call Fetch Data from API once

Fetch is often used to retrieve data from api. But if you want to retrieve this data in every line of the code file, you have to type fetch again and again and iterate through these data. But if i want to call only part of it for example username. Look example:
fetch ('/ API')
         .then ((res) => res.json ())
         .then ((data) => {
         let output = 'Here is my output<br />';
         data.forEach (function (item)
         {
         output + = `$ {item.username}`;
         })
         document.getElementById ('here'). innerHTML = output;
         })
if i want to call $ {item.username} in each place in the file without calling fetch.
How to save these data in a variabel and call it every time i want.
Sorry for my bad English.
But if you want to retrieve this data in every line of the code file, you have to type fetch again and again and iterate through these data.
You can call fetch one time and keep the response in your local state, things like username usually stay the same so you should not call that endpoint again just to fetch a username because you can take it from your previous response.
It depends on your framework, but you can write window.username = usernameFromFetch on your first call, and you will have access to it every where (which is not the best aproach but it will work).
From what I understand through your question is that you want to access variable item.username globally and from the code it looks to be an array of users with username property.
In this case, you can define a variable object
var data = {
usernames = []
};
before your fetch function and the value will be initialized to an empty array and as soon as you fetch('/API') users you can do something like this
data.forEach (function (item)
{
data.usernames.push(item.username);
})
data.usernames will have your usernames throughout your current JS file.
access by
data.usernames[0]
data.usernames[1]
data.usernames[2] or
let output = 'Here is my output<br />';
data.usernames.forEach(function(username){
output + = `$ {username}`;
})
document.getElementById('here').innerHTML = output;`
You can use a memoization function to save the result:
// Stores in memory pairs of request urls (arguments) and its result
const memo = (callback) => {
const cache = new Map();
return (...args) => {
const selector = JSON.stringify(args);
if (cache.has(selector)) return cache.get(selector);
const value = callback(...args);
cache.set(selector, value);
return value;
};
};
// Memoize the fetch function. This function 'cachedRequest' should be used across your application
const cachedRequest = memo(fetch);
// Your url to call
const URL = "/API";
// First time the fetch is called, the result will be stored in the memory of 'memo' function. Second time its called, it will be retrieved from the previous in memory result and the fetch call wont be done
cachedRequest(URL).then((response) => {
console.log({response});
}).catch((error) => {
console.log({error});
})
Try this using localStorage:
fetch ('/ API')
.then ((res) => res.json ())
.then ((data) => {
// Store
localStorage.setItem("localData", data);
// Retrieve
var data = localStorage.getItem("localData");
let output = 'Here is my output<br />';
data.forEach (function (item)
{
output + = `$ {item.username}`;
})
document.getElementById ('here'). innerHTML = output;
})
Or use sessionStorage:
fetch ('/ API')
.then ((res) => res.json ())
.then ((data) => {
// Store
sessionStorage.setItem("localData", data);
// Retrieve
var data = sessionStorage.getItem("localData");
let output = 'Here is my output<br />';
data.forEach (function (item)
{
output + = `$ {item.username}`;
})
document.getElementById ('here'). innerHTML = output;
})

How do I use an Axios response as a parameter in a for loop?

I'm working on writing a script that will retrieve data from an API. I need to get all instances of the data in the API but the API limits me to 250 results at a time. The response also gives me an offset number which I can use in a new API call to get the next set of results. I know how many calls to the API I need to make and my thought was to use a variable in params so that I could update the offset number after every call to the API. The issue always falls back on actually setting the variable and getting it to be used when the loop runs again.
I am able to run the ajax request and return the data and then set the offset to a variable. I've tried creating an initial API call and then returning the response and then using that returned value in a new function inside of a for loop. That seems like it's the closest solution to me but every time the loop runs, the variable resets to null and I get the initial offset value back.
Here is the most recent code:
for (iteration = 1; iteration < 4; iteration++) {
let offsetValue;
pullWriteData = offsetValue => {
return axios({
method: "get",
url: "https://api.hubapi.com/engagements/v1/engagements/paged",
params: {
hapikey: "API_KEY_HERE",
limit: 250,
offset: offsetValue
}
}).then(response => {
return response.data;
});
};
pullWriteData().then(data => {
offsetValue = data.offset;
console.log("New offset value: " + offsetValue);
return offsetValue;
});
}
this code returns:
New offset value: 12345678
New offset value: 12345678
New offset value: 12345678
I've also built a second Axios call in the .then of my pullWriteData() call and that works but that certainly defeats the purpose of writing a program to do that for me.
My expectation is that this will run the initial API call, write the data to a file, update the offsetValue, run the API call again with the new offsetValue, write that response to a file, update the offsetValue and repeat until I've pulled all of the data.
As far as writing the data to a file, I already am able to do that using fs. It's just getting the offsetValue value to update that seems to be vexing me.
Thanks in advance for any guidance.
Using async/await, you could do the following with promises to ensure you're getting the next offset each time and setting it within the loop so the next chained async request passes the new offset to the request.
Note: that with your current iteration, you will only make 3 requests, starting with no offset= in the GET request, and then the next 2 requests will use the returned offset value from the first and second requests respectively, and the 3rd returned offset value isn't used as another request isn't made.
const getEngagements = (offsetValue) => {
return new Promise((resolve, reject) => {
axios({
method: "get",
url: "https://api.hubapi.com/engagements/v1/engagements/paged",
params: {
hapikey: "demo",
limit: 250,
offset: offsetValue
}
}).then(response => {
return resolve(response.data);
}).catch(error => {
return reject(error.message)
})
})
}
const startPaging = async() => {
let offsetValue;
for (let num of [1, 2, 3]) {
await getEngagements(offsetValue).then((data) => {
offsetValue = data.offset;
console.log("New offset value: " + offsetValue);
})
}
/* You could also do a while() loop for while
* dataHasMore = true from response data, if you
* weren't doing a fixed iteration..
*/
/*
let dataHasMore = true;
while (dataHasMore) {
await getEngagements(offsetValue).then((data) => {
dataHasMore = data.hasMore;
offsetValue = data.offset;
console.log("New offset value: " + offsetValue);
})
}
*/
console.log('Done');
}
startPaging();
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
I think you can adjust this example for your needs:
(async () => {
const times = Array.from({ length: 4 }, (_, i) => i));
let offset;
for (let i of times) {
offset = await pullWriteData(offset);
}
})();
function pullWriteData(offsetValue) {
return axios({
method: 'get',
url: 'https://api.hubapi.com/engagements/v1/engagements/paged',
params: {
hapikey: 'API_KEY_HERE',
limit: 250,
offset: offsetValue
}
}).then(response => response.data);
}

How to prevent fetch with same URL being called twice in node.js

I have x number of calls to be made to back-end. Some of them are to same URLs. I am caching the results. But my problem is thatIf I call loadCached immediately twice (or several times) with a same URL, it will actually call fetch twice as well, as cache does not have url before the first fetch is resolved. So, caching works only when one fetch is successfully completed (=resolved). How could I improve the code to wait for the first fetch to be resolved to avoid duplicate query?
function loadCached(url) {
let cache = loadCached.cache || (loadCached.cache = new Map());
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache[url] = text;
return text;
});
}
I'm using promise.all() to wait for loadCached to resolve.
You need to cache entire promise:
function loadCached(url) {
let cache = loadCached.cache || (loadCached.cache = new Map());
let promise;
if (cache.has(url)) {
promise = cache.get(url)
} else {
promise = fetch(url)
cache.set(url, promise)
}
return promise
.then(response => response.text())
}
Also note that in order to set new value with map, you need to use set method, cache[url] is incorrect.

Categories

Resources