Changing template literals variable's value inside JavaScript - javascript

I have been making a pokedex web app just for fun, but i'm struggling to change the pokemon's atributes so that the user can see multiple pokemon. I'm using the PokeAPI and made the url change with a template literal:
let pokeNumber = 1
let url = `https://pokeapi.co/api/v2/pokemon/${pokeNumber}
console.log(url)
function getPokemon() {
fetch(url)
.then((response) => {
return response.json();
})
.then((data) => {
console.clear()
console.log(data)
document.querySelector('.poke_name').innerHTML = data['name'].toUpperCase()
document.querySelector('.poke__peso').innerHTML = `Peso: ${data['weight']}kg`
document.querySelector('.poke__altura').innerHTML = `Altura: ${data['height']}m`
let img = data['sprites']['front_default']
document.querySelector('.poke_img').setAttribute('src', img)
})
}
getPokemon()
document.querySelector('.btn_forw').onclick = () => {
console.log(url)
pokeNumber++;
console.log(pokeNumber)
}
document.querySelector('.btn_backw').onclick = () => {
console.log(url)
pokeNumber--;
console.log(pokeNumber)
}

The variables inside a template string are not stored by reference, instead they are populated immediately when declaring that variable.
For example
let userId = 1;
let url = `/user/${userId}`
console.log(url); // "/user/1"
// we're only changing the `userId` variable - not the `url` variable
userId = 2;
console.log(url); // "/user/1"
// we can then re-declare the `url` variable the same way
url = `/user/${userId}`
console.log(url); // "/user/2"
So generally what you need to do is re-declare the url variable each time you change any variable that it used to construct that string.
Alternatively, you could put it in a function and call that each time, e.g.
function getUrl(){
return `https://pokeapi.co/api/v2/pokemon/${pokeNumber}`
}
// or ES6 one liner
const getUrl = () => `https://pokeapi.co/api/v2/pokemon/${pokeNumber}`
// then instead of using `url`, replace it with the function call `getUrl()`
function getPokemon() {
fetch(getUrl())
.then((response) => {
return response.json();
})
}
// alternatively, if you are not using the `url`
// variable outside the `getPokemon` function,
// you could re-define that within it:
function getPokemon() {
let url = `https://pokeapi.co/api/v2/pokemon/${pokeNumber}`;
fetch(url)
.then((response) => {
return response.json();
})
}

Related

Firebase - how to await getDownloadURL

I have a function that retrieves the data from a document correctly. However, one image has the URL as it's field already. The other image only has a firebase image reference. Before I proceed to another function, I need to wait until the download URL has been fetched. I've attempted it below without much luck, and I'm not entirely sure i've stuck the async in the right place either.
getPhoto(user_id: string) {
this._subscription = this._activatedRoute.params.pipe(
switchMap(params => {
return this.service.getPhoto(params);
})
).subscribe(async(result) => {
const imageOne = result.imageOne;
// Need to await the download URL here
const imageTwo = this.blah(result.imageTwoRef)
this.otherFunction(imageOne, imageTwo)
});
}
blah(reference){
var storage = firebase.storage();
var imageTwo = reference;
var imagePathRef = storage.ref().child(imageTwo);
imagePathRef.getDownloadURL().then((url) => {
return url;
});
}
Using the async keyword only works on function, and by doing so, it will return a promise. So your usage is correct in that instance.
You can use await only in an async function and next to a promise call. It will stop the execution until your promise get resolved.
I think you are almost done. Try it like this and let me know:
getPhoto(user_id: string) {
this._subscription = this._activatedRoute.params.pipe(
switchMap(params => {
return this.service.getPhoto(params);
})
).subscribe(async(result) => {
const imageOne = result.imageOne;
// Need to await the download URL here
const imageTwo = await this.blah(result.imageTwoRef)
this.otherFunction(imageOne, imageTwo);
});
}
async blah(reference){
var storage = firebase.storage();
var imageTwo = reference;
var imagePathRef = storage.ref().child(imageTwo);
const url = await imagePathRef.getDownloadURL();
return url;
}

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;
})

Unable to receive proper data from the promise function

I am trying to scrap wikipedia page to fetch list of airlines by first scrapping first page and then going to each individual page of airline to get the website url. I have divided the code in two functions. One to scrap main page and get a new url, and second function to scrap another page from the created url to get the website name from that page. I have used request-promise module for getting the html and then cheerio to parse the data.
export async function getAirlinesWebsites(req,res) {
let response = await request(options_mainpage);
console.log(`Data`);
let $ = cheerio.load(response);
console.log('Response got');
$('tr').each((i,e)=>{
let children = '';
console.log('inside function ', i);
if($(e).children('td').children('a').attr('class') !== 'new') {
children = $(e).children('td').children('a').attr('href');
let wiki_url = 'https://en.wikipedia.org' + children;
console.log(`wiki_url = ${wiki_url}`);
let airline_url = getAirlineUrl(wiki_url);
console.log(`airline_url = ${airline_url}`);
}
})
And then the getAirlineUrl() function will parse another page based on the provided url.
async function getAirlineUrl(url){
const wiki_child_options = {
url : url,
headers : headers
}
let child_response = await request(wiki_child_options);
let $ = cheerio.load(child_response);
let answer = $('.infobox.vcard').children('tbody').children('tr').children('td').children('span.url').text();
return answer;
})
However when I console log the answer variable in the parent function, I get a [object Promise] value instead of a String. How do I resolve this issue?
Async function return promise.In case of that,you need to use then to get resolved response or use await.
This should work if other part of your code is ok.
export async function getAirlinesWebsites(req, res) {
let response = await request(options_mainpage);
console.log(`Data`);
let $ = cheerio.load(response);
console.log("Response got");
$("tr").each(async (i, e) => {
let children = "";
console.log("inside function ", i);
if ($(e).children("td").children("a").attr("class") !== "new") {
children = $(e).children("td").children("a").attr("href");
let wiki_url = "https://en.wikipedia.org" + children;
console.log(`wiki_url = ${wiki_url}`);
let airline_url = await getAirlineUrl(wiki_url);
console.log(`airline_url = ${airline_url}`);
}
});
}
Since your getAirlineUrl function returns a promise, you need to await that promise. You can't have await nested inside of the .each callback because the callback is not an async function, and if it was it wouldn't work still. The best fix is the avoid using .each and just use a loop.
export async function getAirlinesWebsites(req,res) {
let response = await request(options_mainpage);
console.log(`Data`);
let $ = cheerio.load(response);
console.log('Response got');
for (const [i, e] of Array.from($('tr')).entries()) {
let children = '';
console.log('inside function ', i);
if($(e).children('td').children('a').attr('class') !== 'new') {
children = $(e).children('td').children('a').attr('href');
let wiki_url = 'https://en.wikipedia.org' + children;
console.log(`wiki_url = ${wiki_url}`);
let airline_url = await getAirlineUrl(wiki_url);
console.log(`airline_url = ${airline_url}`);
}
}
}

Get value of return variable after subscribe()

I doing an Async call using the below code, I want to know the value of data that is generated inside the getData() function however, I am getting undefined because the call is not resolved yet. Is there any way to sort it out?
getData(address){
let url = 'https://maps.googleapis.com/maps/api/geocode/json?address='+address+'&key='+this.key;
let call = this.http.get(url).subscribe(data =>{
let data = data.json()
return data;
});
}
//this is undefined because it does not know if the call was finished or not
console.log(this.latlong)
secondMethod(item){
//this is also undefined
this.getData(item.address)
}
Well, here is what you can do to solve it, simply push the data inside an array, then you may retrieve it in other functions.
getData(address){
let array = [];
let url = 'https://maps.googleapis.com/maps/api/geocode/json? address='+address+'&key='+this.key;
let call = this.http.get(url).subscribe(data =>{
let data = data.json()
array.push(data);
});
return array;
}
secondMethod(item){
//now you'll be able to retrieve it !
this.getData(item.address)
}
getData(address){
let url = 'https://maps.googleapis.com/maps/api/geocode/json?address='+address+'&key='+this.key;
return this.http.get(url).map(data => data.json());
})
Then you need to subscribe to get a value.
secondMethod(item){
this.getData(item.address).subscribe(data => {
console.log(data);
});
}
Here's how you could make that work keeping in mind async.
getData(address, callback){
let url = 'https://maps.googleapis.com/maps/api/geocode/json?
address='+address+'&key='+this.key;
let call = this.http.get(url).subscribe(callback);
}
secondMethod(item){
this.getData(item.address, this.thirdMethod.bind(this))
}
thirdMethod(data) {
let data = data.json()
// do stuff
this.something = data['something'];
}

Categories

Resources