I have seen the others questions about this topic, but my problem is slightly different: I have an axios call inside the .then of another axios call, and then I take values from the API that I called with axios and in another .then I render an ejs page, but the first time I execute, it tries to render the page before axios calling are completed, so variable to build the page are undefined, in particular it prints the error 'h1 is undefined' and h1 is the first variable in the ejs document after nameCapitalized which, as you can see from the code, is form the request and not from the axios api call; from the second time it works perfectly, when it works, it prints in order the console.log statements in .then, so the execution order is right, at least from the second execution. This get request is made after clicking a link in the browser.
Here the code:
app.get('/private_:city/', function(req, response) {
const name = req.params.city;
const nameCapitalised = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
let lat;
let lon;
let country;
let jsonObj;
let h1;
let w1;
let t1;
let uv1;
let rp1;
let ws1;
axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${nameCapitalised}&limit=1&appid=${apiKeyOpenWeather}`)
.then(res => {
console.log('first');
lat = res.data[0].lat;
lon = res.data[0].lon;
country = res.data[0].country;
console.log(res.data[0]);
axios.get(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=daily,minutely&units=metric&lang=it&appid=${apiKeyOpenWeather}`)
.then(res => {
console.log('second');
jsonObj = res.data;
h1 = new Date(jsonObj.hourly[0].dt * 1000).getHours();
w1 = jsonObj.hourly[0].weather[0].description;
t1 = jsonObj.hourly[0].temp;
uv1 = jsonObj.hourly[0].uvi;
rp1 = Math.trunc(jsonObj.hourly[0].pop * 100);
ws1 = jsonObj.hourly[0].wind_speed;
})
.then(res => {
console.log('third');
response.render('private.ejs', {
title: nameCapitalised,
t1: t1,
h1: h1,
w1: w1,
uv1: uv1,
rp1: rp1,
ws1: ws1,
});
});
});
});
Try to use only thens...
app.get('/private_:city/', function(req, response) {
const name = req.params.city;
const nameCapitalised = name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
let lat;
let lon;
let country;
let jsonObj;
let h1;
let w1;
let t1;
let uv1;
let rp1;
let ws1;
axios.get(`http://api.openweathermap.org/geo/1.0/direct?q=${nameCapitalised}&limit=1&appid=${apiKeyOpenWeather}`)
.then(async (res) => {
console.log('first');
lat = res.data[0].lat;
lon = res.data[0].lon;
country = res.data[0].country;
console.log(res.data[0]);
return await axios.get(`https://api.openweathermap.org/data/2.5/onecall?lat=${lat}&lon=${lon}&exclude=daily,minutely&units=metric&lang=it&appid=${apiKeyOpenWeather}`)})
.then(res => {
console.log('second');
jsonObj = res.data;
h1 = new Date(jsonObj.hourly[0].dt * 1000).getHours();
w1 = jsonObj.hourly[0].weather[0].description;
t1 = jsonObj.hourly[0].temp;
uv1 = jsonObj.hourly[0].uvi;
rp1 = Math.trunc(jsonObj.hourly[0].pop * 100);
ws1 = jsonObj.hourly[0].wind_speed;
})
.then(res => {
console.log('third');
response.render('private.ejs', {
title: nameCapitalised,
t1: t1,
h1: h1,
w1: w1,
uv1: uv1,
rp1: rp1,
ws1: ws1,
});
});
});
});
I think you should try to change variables order in the ejs document in order to understand if it is a problem of 'h1'
Related
I am trying to do data visualization using javascript to construct bar graphs. I am encountering problems when trying to render the graphs. I keep getting the message 'TypeError: rates is not iterable' in the console which refers to the for loop in the render function. Any idea how to fix this problem?
Thanks!
Here's my code:
console.log('running js for currency rates');
doFetch()
function doFetch(){
fetch('https://api.exchangeratesapi.io/latest')
.then(response => response.json())
.then(data => {
console.log('Got the data!');
console.log(data);
let rates = data.rates;
let base = data.base;
let date = data.date;
console.log(rates);
render(rates)
});
}
function render(rates){
const currencyName = Object.keys(rates);
const values = Object.values(rates);
let chart = document.querySelector('.BarContainer');
chart.innerHTML = "";
for(let rate of rates){
let bar = document.createElement('div');
let baseHeight = 100;
bar.classList.add('Bar');
bar.style.height = baseHeight + 'px';
bar.textContent = currencyName;
chart.appendChild(bar);
}
}
for/of works on iterables, and an Object isn't iterable. You might be looking for for/in.
for/of
for/in
Because rates are literal object. And for of works for Javascript Array. I think you need to use for in instead.
Here is what should be
<!doctype html>
<html>
<body>
<div class="BarContainer"></div>
</body>
<script type='text/javascript'>
console.log('running js for currency rates');
doFetch()
function doFetch(){
fetch('https://api.exchangeratesapi.io/latest')
.then(response => response.json())
.then(data => {
console.log('Got the data!');
console.log(data);
let rates = data.rates;
let base = data.base;
let date = data.date;
console.log(rates);
render(rates)
});
}
function render(rates){
const currencyName = Object.keys(rates);
const values = Object.values(rates);
let chart = document.querySelector('.BarContainer');
chart.innerHTML = "";
for(let rate in rates){
let bar = document.createElement('div');
let baseHeight = 100;
bar.classList.add('Bar');
bar.style.height = baseHeight + 'px';
bar.textContent = currencyName;
chart.appendChild(bar);
}
}
</script>
<html>
I am trying to get API data so i can render it on Webpage using ReactJS. I tried many different ways to fetch api where its stored in array. But i am unable to do it.
Note:https://www.hatchways.io/api/assessment/workers/<worker_id> Here i'm looping so the
worker id gets add the of url.
const fetchAPI = e => {
let array = [];
const api2 = `https://www.hatchways.io/api/assessment/workers/`;
for (var i = 0; i <= 4; i++) {
array.push(api2 + i);
}
return array;
};
console.log(fetchAPI());
Thanks in advance.
You need to hit the url first. Async/Await would be a good choice.
<script>
async function check()
{
var arrayData =[];
var url = "https://www.hatchways.io/api/assessment/workers/";
for(var i=1;i<=4;i++)
{
const response = await fetch(url+""+i);
const myJson = await response.json();
arrayData.push(myJson);
}
console.log(arrayData)
}
check();
</script>
Use Promise.all for example:
const baseURL = 'https://jsonplaceholder.typicode.com';
const fetchUsers = fetch(`${baseURL}/users`);
const fetchPosts = fetch(`${baseURL}/posts`);
Promise.all([fetchUsers, fetchPosts]).then((responses) => {
const responsesToJson = responses.map(response => response.json());
return Promise.all(responsesToJson);
}).then((jsonResponse) => {
const [userResponse, postResponse] = jsonResponse;
console.log(userResponse);
console.log(postResponse);
});
Mediocre javascript developer here and need some help..
I want to make a GET call to a url several times in a for loop.
I am trying to use fetch, but because of the promise/timing situation, I can't figure out how to make it work.
Below is the code and it's entirely possible that the fetch approach doesn't make sense for what I am trying to do. Would appreciate your help either helping me with code or telling me I am idiot and advising an alternative :)
var fromAmt = 100;
var fromOOP = 50;
var fromGM = 50;
var fromCur = "USD"
var toCur = ["USD","EUR","INR","GBP","SGD"];
var adjAmt = [];
async function getConversionAsync(fcur,tcur,amt)
{
let response = await fetch('https://data.fixer.io/api/convert?access_key=xyxyxyxyxyxyxy&from=' + fcur + '&to=' + tcur + '&amount=' + amt);
let data = await response.json()
return data;
}
for (i = 0; i < toCur.length; i++) {
getConversionAsync(fromCur,toCur[0].toString(),fromAmt)
.then(data => display(data));
}
function display(thing){
adjAmt.push(thing.result);
}
document.getElementById("something").innerHTML = adjAmt[0].toString();
In your example, document.getElementById("something").innerHTML = adjAmt[0].toString(); is executed before anything is pushed to adjAmt. You need to wait for the loop calls to finish before displaying a result, and for this you could wrap everything inside an async function.
const fromAmt = 100;
const fromOOP = 50;
const fromGM = 50;
const fromCur = 'USD';
const toCur = ['USD', 'EUR', 'INR', 'GBP', 'SGD'];
const adjAmt = [];
const getConversionAsync = async (fcur, tcur, amt) => {
const response = await fetch(`https://data.fixer.io/api/convert?access_key=xyxyxyxyxyxyxy&from=${fcur}&to=${tcur}&amount=${amt}`);
return response.json();
}
function display(thing) {
adjAmt.push(thing.result);
}
(async () => {
for (i = 0; i < toCur.length; i += 1) {
const data = await getConversionAsync(fromCur, toCur[0], fromAmt);
display(data);
}
document.getElementById('something').innerHTML = adjAmt[0].toString();
})();
Some small changes to make it work without the API call, but you'll want to access the correct index in your loop. I don't know what the exact output you're wanting here but in this case I just joined all the values in the array.
Additionally, the setting of innerHTML needs to be done once all the values are retrieved from the API, so I would even suggest doing that when the loop terminates, or some other "done" type event.
Additionally, you can use Promise.all instead of a loop, which is what I would go with personally.
var fromAmt = 100;
var fromOOP = 50;
var fromGM = 50;
var fromCur = "USD"
var toCur = ["USD", "EUR", "INR", "GBP", "SGD"];
var adjAmt = [];
async function getConversionAsync(fcur, tcur, amt) {
let response = await sampleRequest()
/* let data = await response.json() */
return response;
}
for (i = 0; i < toCur.length; i++) {
const data = getConversionAsync(fromCur, toCur[i].toString(), fromAmt).then(data => {
display(data)
})
}
function display(thing) {
adjAmt.push(thing);
document.getElementById("something").innerHTML = adjAmt.join(', ')
}
function sampleRequest() {
return new Promise((resolve, reject) => {
resolve(Math.round(Math.random() * 1000))
})
}
<div id="something"></div>
When I click the search button for the first time it gives two errors:
main.js:68 GET https://cors-anywhere.herokuapp.com/https://api.darksky.net/forecast/API_key/ 404 (Not Found)
Uncaught (in promise) SyntaxError: Unexpected token N in JSON at position 0.
But when I click the search button second time the error goes away.
so I have to click the search button two times to get the data from the API.
index.html
<form class="searchForm" method="POST">
<div class="form-div">
<label for="loaction">Enter a location</label>
<input type="text" class="searchForm-input" id="loaction" placeholder="Location">
<button type="submit">Search</button>
</div>
</form>
<div class="echo">--</div>
<div class="location">
<h1 class="location-timezone">Timezone</h1>
</div>
<div class="temperature">
<div class="degree-section">
<h2 class="temperature-degree">34</h2>
<span>F</span>
</div>
</div>
main.js
let lat1 = '';
let form = document.querySelector('.searchForm');
form.addEventListener('submit', handleSubmit);
function handleSubmit(event) {
event.preventDefault();
const input = document.querySelector('.searchForm-input').value;
// remove whitespace from the input
const searchQuery = input.split(' ').join('+');
// print `searchQuery` to the console
console.log(searchQuery);
let geocodeURL = `https://maps.googleapis.com/maps/api/geocode/json?
address=${searchQuery}&key=api_key`;
fetch(geocodeURL)
.then(response => {
return response.json();
})
.then(data => {
let max = data.results[0].geometry.location;
console.log(max);
let max1 = max.lat + ',' + max.lng;
console.log(max1);
lat1 = max1;
console.log(lat1);
})
console.log(geocodeURL);
let temperatureDegree = document.querySelector('.temperature-degree');
let locationTimezone = document.querySelector('.location-timezone');
let echos = document.querySelector('.echo');
echos.textContent = searchQuery;
const proxy = 'https://cors-anywhere.herokuapp.com/';
const api =
`${proxy}https://api.darksky.net/forecast/aki_key/${lat1}`;
fetch(api)
.then(response => {
return response.json();
})
.then(data => {
console.log(data);
const {temperature} = data.currently;
temperatureDegree.textContent = temperature;
locationTimezone.textContent = data.timezone;
})
}
You have two asynchronous operations where the second needs to use the results of the first AJAX operation to continue:
fetch(geocodeURL)
.then(response => {
return response.json();
})
.then(data => {
let max = data.results[0].geometry.location;
console.log(max);
let max1 = max.lat+',' + max.lng;
console.log(max1);
lat1 = max1; <-- lat1 is used in second AJAX call
console.log(lat1);
})
console.log(geocodeURL);
And some lines later:
const proxy = 'https://cors-anywhere.herokuapp.com/';
const api =
`${proxy}https://api.darksky.net/forecast/aki_key/${lat1}`; // <-- lat1 will be undefined
So when you click the search button the first will fire and when it returns it will fill the lat1 variable. As this is the result of a Promise, it will fire as soon as it is fulfilled while in the meantime the main thread will contine and perform the next fetch(api) statement without waiting for the lat1 to be set. Simply move the second AJAX call into the Promise resolution:
event.preventDefault();
const input = document.querySelector('.searchForm-input').value;
// remove whitespace from the input
const searchQuery = input.split(' ').join('+');
// print `searchQuery` to the console
console.log(searchQuery);
let geocodeURL = `https://maps.googleapis.com/maps/api/geocode/json?
address=${searchQuery}&key=api_key`;
fetch(geocodeURL)
.then(response => {
return response.json();
})
.then(data => {
let max = data.results[0].geometry.location;
console.log(max);
let max1 = max.lat+',' + max.lng;
console.log(max1);
lat1 = max1;
console.log(lat1);
let temperatureDegree = document.querySelector('.temperature-
degree');
let locationTimezone = document.querySelector('.location-timezone');
let echos = document.querySelector('.echo');
echos.textContent = searchQuery;
const proxy = 'https://cors-anywhere.herokuapp.com/';
const api =
`${proxy}https://api.darksky.net/forecast/aki_key/${lat1}`;
fetch(api)
.then(response => {
return response.json();
})
.then(data => {
console.log(data);
const {temperature} = data.currently;
temperatureDegree.textContent = temperature;
locationTimezone.textContent = data.timezone;
})
}
})
console.log(geocodeURL);
Following is what I'm trying to do this in nodejs. The Rest API takes a city name as an input. I am trying to get the latitude and longitude using the geocode API for the input city. then, using the latitude and longitude, I am trying to get a list of closest cities using another API. then, for all those cities, I am getting the weather report, then for those cities, I am getting whether there is water and I am returning this back as a JSON.
As you can see, there is a lot of then and the goal of this exercise is to avoid nested callbacks.
I am using async/await which is supposed to have eliminated the nested then functions. But I don't see another way of doing this. The complete code snippet is below. The ugly part I am trying to fix is requester.makeRequest()
Following is just a snippet of the necessary code and not the complete working code. Any help on how to untangle this would be greatly appreciated.
app.get('/search', function(req, res, next) {
const requester = {
lastRequest: new Date(),
makeRequest: async function(url) {
const response = await fetch(url);
const json = await response.json();
return json;
}
};
requester.makeRequest(geocode_url +`?locate=${req.query.q}&json=1`
+ geocode_token)
.then(function(city){
var final_result = []
var lat = city.latt;
var long = city.longt;
// request to get list of cities closer to that location,
//takes latitude and longitude as parameters
requester.makeRequest(metaweather_url + '?lattlong='
+ lat + ',' + long)
.then(function(closer_cities) {
var cities_len = closer_cities.length
for(i = 0; i < closer_cities.length; i++) {
woeid = closer_cities[i].woeid
//request to get weather using woeid parameter
requester.makeRequest(woeid_url + woeid)
.then(function(weather) {
var lattlong = weather.latt_long;
requester.makeRequest(onwater_url+ lattlong +
'?access_token=' + water_access_token)
.then(function(onwater) {
var temp = Object.assign(weather, onwater)
final_result.push(temp)
if (final_result.length == cities_len) {
res.status(200).json({error: false,
data: {message: final_result}})
}
})
})
}
})
})
})
I would say you still need one then
requester.makeRequest(geocode_url +`?locate=${req.query.q}&json=1`
+ geocode_token)
.then(async function(city){
var final_result = []
var lat = city.latt;
var long = city.longt;
// request to get list of cities closer to that location,
//takes latitude and longitude as parameters
closer_cities = await requester.makeRequest(metaweather_url + '?lattlong='+ lat + ',' + long);
var cities_len = closer_cities.length;
for(i = 0; i < closer_cities.length; i++) {
woeid = closer_cities[i].woeid
//request to get weather using woeid parameter
weather = await requester.makeRequest(woeid_url + woeid)
var lattlong = weather.latt_long;
onwater = await awaitrequester.makeRequest(onwater_url+ lattlong + '?access_token=' + water_access_token)
var temp = Object.assign(weather, onwater)
final_result.push(temp)
if (final_result.length == cities_len) {
res.status(200).json({error: false, data: {message: final_result}})
}
}
})
Edit: I don't really think my answer is relevant for your problem sorry
for this line : requester.makeRequest ... .then(function(city){
replace .then(function(city){ with var city = await requester.makeRequest , city will have the fulfilled value of the promise, do this for the rest of thens :
( keep in mind that await is only used inside an async function, you can use an iife )
(async () => {
var city = await requester.makeRequest(`${geocode_url}?locate=${req.query.q}&json=1${geocode_token}`);
var final_result = []
var lat = city.latt;
var long = city.longt;
// request to get list of cities closer to that location,
//takes latitude and longitude as parameters
var closer_cities = await requester.makeRequest(`${metaweather_url}?lattlong=${lat},${long}`);
var cities_len = closer_cities.length;
for (i = 0; i < closer_cities.length; i++) {
woeid = closer_cities[i].woeid
//request to get weather using woeid parameter
var weather = await requester.makeRequest(woeid_url + woeid);
var lattlong = weather.latt_long;
var onwater = await requester.makeRequest(`${onwater_url}${lattlong}?access_token=${water_access_token}`);
var temp = Object.assign(weather, onwater)
final_result.push(temp)
if (final_result.length == cities_len) {
res.status(200).json({
error: false,
data: {
message: final_result
}
})
}
}
})();
then is misused in the first place because it results in callback hell. Promises are callback-based but they support chaining which is supposed to eliminate nested callbacks.
It should be:
requester.makeRequest(geocode_url +`?locate=${req.query.q}&json=1` + geocode_token)
.then(function(city){
var final_result = []
var lat = city.latt;
var long = city.longt;
return requester.makeRequest(metaweather_url + '?lattlong='
+ lat + ',' + long)
})
.then(function(closer_cities) {
...
});
If there's a promise inside then, it should be returned. This way there's no more than a single level of callback nesting.
await is syntactic sugar for then, and rejections should be handled as well:
app.get('/search', function(req, res, next) {
try {
...
const city = await requester.makeRequest(geocode_url +`?locate=${req.query.q}&json=1`
+ geocode_token);
var final_result = []
var lat = city.latt;
var long = city.longt;
const closer_cities = await requester.makeRequest(metaweather_url + '?lattlong='
+ lat + ',' + long);
...
} catch (err) {
next(err)
}
});
When calling async functions you are not supposed to use .then(...) construct...
Simply let result = await myAsynchronousFunction(a, b, c); ...