-2

I'm creating a simple web app that fetches data asynchronously from three web apis. One is for location, one for weather and one for stock images. My files are as follow:

Index.html:

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <title>Weather Journal</title>
</head>

<body>
  <div id="app">
    <div class="holder headline">
      Weather Journal App
    </div>
    <form id="userInfo">
      <div class="holder zip">
        <label for="city">Enter City here</label>
        <input type="text" id="city" placeholder="enter city here" required>
      </div>
      <div class="holder feel">
        <label for="date">Enter departure date</label>
        <input type="datetime-local" id="date" required>
        <button id="submitBtn" type="submit"> Generate </button>
      </div>
    </form>
    <div class="holder entry">
      <div class="title">Details</div>
      <div>
        <img id="city-pic" src="" alt="your city">
      </div>
      <div id="entryHolder">
        <div><b>Temperature for next 16 days:</b></div>
        <ul id="entries">
          <div id="temp"></div>
          <div id="time"></div>
        </ul>

      </div>
    </div>
  </div>
  <script src="app.js" type="text/javascript"></script>

</body>

</html> 

app.js:

const present = new Date();

const submitBtn = document.getElementById("submitBtn");

submitBtn.addEventListener("click", (e) => {
    e.preventDefault();
    const city = document.getElementById("city").value;
    const departure = document.getElementById("date").value;
    const [depart_date, depart_time] = departure.split("T")
    const [depart_year, depart_month, depart_day] = depart_date.split("-")
    const [depart_hour, depart_minute] = depart_time.split(":")

    const future = new Date(depart_year, depart_month - 1, depart_day, depart_hour, depart_minute);

    if (city !== "" || departTime !== "" || future < present) {

        document.getElementById("time").innerHTML = `Departure in ${Math.ceil((future - present) / 3600000 / 24)} days`

        getCity(geoURL, city, geoUsername)
            .then(function (data) {
                return getWeather(weatherURL, weatherKey, data["geonames"][0]['lat'], data["geonames"][0]['lng'])
            }).then(weatherData => {
                return postWeatherData("/addWeather", { temp: weatherData['data'][i]['temp'], datetime: weatherData['data'][i]['datetime'] })
            }).then(function () {
                return receiveWeatherData()
            }).catch(function (error) {
                console.log(error);
                alert("Please enter a valid city and a valid time");
            })
        getPictures(city, pixabayURL, pixabayKey)
            .then(function (picsData) {
                const total = picsData['hits'].length
                const picIndex = Math.floor(Math.random() * total)
                return postPictureData("/addPicture", { pic: picsData['hits'][picIndex]["webformatURL"] })
            })
            .then(function () {
                return receivePictureData()
            }).catch(function (error) {
                console.log(error);
                alert("No pictures found")
            })

    }
})

const getCity = async (geoURL, city, geoUsername) => {
    const res = await fetch(`${geoURL}q=${city}&username=${geoUsername}`);
    try {
        const cityData = await res.json();
        return cityData;
    }
    catch (error) {
        console.log("error", error);
    }
}


const postWeatherData = async (url = "", data = {}) => {
    const response = await fetch(url, {
        method: "POST",
        credentials: "same-origin",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            temp: data.temp,
            datetime: data.datetime
        })
    });

    try {
        const newData = await response.json();
        console.log(newData)
        return newData;
    }
    catch (error) {
        console.log(error);
    }
}

const receiveWeatherData = async () => {
    const request = await fetch("/allWeather");
    try {
        const allData = await request.json()
        const node = document.createElement("li");
        const textnode = document.createTextNode("DATE: " + allData['datetime'] + "\t" + "TEMPERATURE: " + allData['temp']);
        node.appendChild(textnode);
        document.getElementById("entries").appendChild(node);
    }
    catch (error) {
        console.log("error", error)
    }
}

const getWeather = async (weatherURL, weatherKey, lat, lon) => {
    const res = await fetch(`${weatherURL}&lat=${lat}&lon=${lon}&key=${weatherKey}`);
    try {
        const weatherData = await res.json();
        return weatherData;
    }
    catch (error) {
        console.log("error", error);
    }
}

const getPictures = async (city, pixabayURL, pixabayKey) => {
    const query = city.split(" ").join("+");
    const res = await fetch(`${pixabayURL}key=${pixabayKey}&q=${query}`);
    try {
        const picsData = await res.json();
        return picsData;
    }
    catch (error) {
        console.log("error", error)
    }
}

const receivePictureData = async () => {
    const request = await fetch("/allPictures");
    try {
        const allData = await request.json()
        document.getElementById("city-pic").src = allData['pic'];
    }
    catch (error) {
        console.log("error", error)
    }
}

const postPictureData = async (url = "", data = {}) => {
    const response = await fetch(url, {
        method: "POST",
        credentials: "same-origin",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            pic: data.pic
        })
    });

    try {
        const newData = await response.json();
        return newData;
    }
    catch (error) {
        console.log(error);
    }
}

server.js:

// Setup empty JS object to act as endpoint for all routes
cityData = {};
weatherData = {};
picturesData = {};

// Require Express to run server and routes
const express = require('express');
const bodyParser = require('body-parser');
const cors = require('cors');

// Start up an instance of app
const app = express();

/* Middleware*/
//Here we are configuring express to use body-parser as middle-ware.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

// Cors for cross origin allowance
app.use(cors())
// Initialize the main project folder
app.use(express.static('dist'));
app.use(express.static('website'));

app.get("/all", function sendData(req, res) {
    res.send(cityData);
})

app.get("/allWeather", function sendWeather(req, res) {
    res.send(weatherData);
})

app.get("/allPictures", function sendPictures(req, res) {
    res.send(picturesData);
})


app.post("/addWeather", (req, res) => {
    weatherData['temp'] = req.body.temp;
    weatherData['datetime'] = req.body.datetime;
    res.send(weatherData);
})

app.post("/addPicture", (req, res) => {
    picturesData['pic'] = req.body.pic;
    res.send(picturesData);
})

// Setup Server
app.listen(3000, () => {
    console.log("App listening on port 3000")
    console.log("Go to http://localhost:3000")
})

The server uses node, express, cors, and body-parser as the tools to create it.

I have not included the api keys or usernames. Right now I need to loop over the weather data that is return from the api (which fetches 16 days of data). The code :

.then(weatherData => {
                return postWeatherData("/addWeather", { temp: weatherData['data'][i]['temp'], datetime: weatherData['data'][i]['datetime'] })

should use the 'i' variable to loop over all the possible 16 entries for a certain location. Right now if run the app with 0 in place of 'i' it just gives the temperature for the next day. I want to get the weather data for 16 days and append it to the html 'ul' that I have in the document. Can someone guide me. This is the last step in the project that I need to complete by 10 november!

EDIT:

I tried using this and subsituted forLoop() instead the 'thens' but i get an error:

const forLoop = async () => {
    for (i = 0; i < 16; i++) {
        try {
            coords = await getCity(geoURL, city, geoUsername)
            weatherData = await getWeather(weatherURL, weatherKey, coords["geonames"][0]['lat'], coords["geonames"][0]['lng'])
            await postWeatherData("/addWeather", { temp: weatherData['data'][i]['temp'], datetime: weatherData['data'][i]['datetime'] })
            await receiveWeatherData(i);
        }

        catch (error) {
            console.log(error);
            // alert("Please enter a valid city and a valid time");
        }
    }
}

The error is: "16app.js:156 TypeError: Cannot read properties of undefined (reading 'lat') at forLoop (app.js:150:89)"

  • [Don't wrap every single bit of your code in `try { … } catch(err) { console.error(err) } `!](https://stackoverflow.com/questions/63251622/should-i-avoid-try-catch-in-every-single-async-await-on-node-js) – Bergi Oct 26 '22 at 08:43
  • 1
    Don't use `then`s in that function. Make it use `async`/`await` like the others, and a loop will be simple to write – Bergi Oct 26 '22 at 08:46
  • Where does `i` come from? It's not defined in the code you showed. My guess is that it comes from `for (var i = ...)`, and the solution is then to change `var` to `let`. ([Here is why and how that works.](https://youtu.be/Nzokr6Boeaw)) – CherryDT Oct 26 '22 at 09:12
  • I'm familiar with the difference between var and let.. But that's not the issue... I was gonna use let anyways... but how can I use the for loop in an async function.. that's where I'm stuck – Rohan Asif Oct 26 '22 at 09:37
  • @Bergi How can I do this? ... Can you please send a code snippet... I'm really lost – Rohan Asif Oct 26 '22 at 10:26

3 Answers3

0

A series of promises can be created using Array.map() over received weather data, and a series of promises can be resolved using Promise.all(). The returned output would be an array of received temperature information. Please see the below code

(PromiseLikeObject)
    .then(weatherData => {
        const promiseCollection = weatherData['data'].map(d => postWeatherData("/addWeather", { temp: d.temp, datetime: d.datetime }));
        return Promise.all(promiseCollection);
    });
Wazeed
  • 1,230
  • 1
  • 8
  • 9
  • Well I understand what you're saying but when I replaced the code, i still get the temperature and date for after 16 days. – Rohan Asif Oct 26 '22 at 08:53
  • @RohanAsif this is due to `/addWeather` route in node api is updating same `weatherData` reference, due to which `/allWeather` is getting lastly added weather. – Wazeed Oct 26 '22 at 09:12
  • So what's your suggestion... I'm sorry if sound like a newbie... I'm still learning – Rohan Asif Oct 26 '22 at 09:18
0

If I understood you right, I think you could just map over the data and display it in DOM, just adjust the to the array you receive.

    const postWeatherData = async (url = "", data = {}) => {
    const response = await fetch(url, {
        method: "POST",
        credentials: "same-origin",
        headers: {
            "Content-Type": "application/json",
        },
        body: JSON.stringify({
            temp: data.temp,
            datetime: data.datetime
        })
    });

    try {
        const newData = await response.json();
        let entryHolder = document.getElementById("entryHolder");
          const weather = newData.map((data, index) => {
            return `<ul id="entries">
      <div id="temp">${data.temp}</div>
      <div id="time">${data.time}</div>
    </ul>`
        }).join('')
        entryHolder.innerHTML = weather 
    }
    catch (error) {
        console.log(error);
    }
}
  • Hi thanks for the suggestion. Although i'm getting an error : "TypeError: newData.map is not a function at postWeatherData" – Rohan Asif Oct 26 '22 at 09:21
  • @RohanAsif can you provide the newData object you receive from the backend – Oleg Oct 26 '22 at 09:30
  • `{temp: 15.8, datetime: '2022-10-26'} datetime : "2022-10-26" temp : 15.8` [[Prototype]] : Object – Rohan Asif Oct 26 '22 at 09:33
  • What if I create an async function called forLoop and run all the async functions with "thens" inside of that forLoop function? – Rohan Asif Oct 26 '22 at 10:49
0

I have fixed it with for loop:

const forLoop = async () => {
    for (i = 0; i < 16; i++) {
        try {
            const city = await document.getElementById("city").value;
            const coords = await getCity(geoURL, city, geoUsername)
            const weatherData = await getWeather(weatherURL, weatherKey, coords["geonames"][0]['lat'], coords["geonames"][0]['lng'])
            postWeatherData("/addWeather", { temp: weatherData['data'][i]['temp'], datetime: weatherData['data'][i]['datetime'] })
            receiveWeatherData(i)
        }
        catch (error) {
            console.log(error);
            // alert("Please enter a valid city and a valid time");
        }
    }
}