1

I know it's a very popular and many times answered topics, but I

couldn't fully understand it's application in my case.

I am learning my way through Javascript, and I am stuck trying to return a value from async function. Essentially, I want my function to take a city name and return its current temperature from APIXU Weather API (https://www.apixu.com).

Then, I wanted to use this function inside my other Google API function. This function takes id of the clicked marker, finds it's city name in the array of places, and displays information in an infowindow, such as title of the place, weather of a city and Google Streetview. My initial plan was to use getWeather function inside populateInfoWindow to find the weather of a city. Is it possible to achieve it? Because from what I understand using asynchronous function inside synchronous, will make the latest one asynchronous. Another possible route I was going to choose is to get the weather of all places in the array on document load and just take the information from the array in populateInfoWindow function. But I am afraid that this will be repetitive and unnecessary.

To your opinion, what would be the best way to accomplish it and how to avoid similar problems?

Here is the code:

//Get current forecast from weather API
        function getWeather(city) {
            var weatherAPIXU = "http://api.apixu.com/v1/current.json?key=XXXXXXXXXXXXXXX&q=" + city;
            $.getJSON(weatherAPIXU, function(data) {
                var forecast = data.current.temp_c;
                var curWeather = forecast + '° C';
                 return curWeather
            });
        }

// Return a city name that matches a marker id
function getCityName(locations, marker) {
    for (var i = 0, iLen = locations.length; i < iLen; i++) {
        if (locations[i].id == marker.id) return locations[i].city;
    }
}

// This function populates the infowindow when the marker is clicked. It'll only allow
// one infowindow which will open at the marker that is clicked, and populate based
// on that markers position.
function populateInfoWindow(marker, infowindow) {
    // Check to make sure the infowindow is not already opened on this marker.
    if (infowindow.marker != marker) {
        // Clear the infowindow content to give the streetview time to load.
        infowindow.setContent('');
        infowindow.marker = marker;
        // Make sure the marker property is cleared if the infowindow is closed.
        infowindow.addListener('closeclick', function() {
            infowindow.marker = null;
        });
        var streetViewService = new google.maps.StreetViewService();
        var radius = 50;
        var city = getCityName(locations, marker);
        console.log(city);
        var weatherInCity = getWeather(city);
        console.log(weatherInCity);
        // In case the status is OK, which means the pano was found, compute the
        // position of the streetview image, then calculate the heading, then get a
        // panorama from that and set the options
        var getStreetView = function(data, status) {
            if (status == google.maps.StreetViewStatus.OK) {
                var nearStreetViewLocation = data.location.latLng;
                var heading = google.maps.geometry.spherical.computeHeading(
                    nearStreetViewLocation, marker.position);
                infowindow.setContent('<div>' + marker.title + '' + weatherInCity +
                    '</div><div id="pano"></div>');
                var panoramaOptions = {
                    position: nearStreetViewLocation,
                    pov: {
                        heading: heading,
                        pitch: 30
                    }
                };
                var panorama = new google.maps.StreetViewPanorama(
                    document.getElementById('pano'),
                    panoramaOptions);
            } else {
                infowindow.setContent('<div>' + marker.title +
                    '</div>' +
                    '<div>No Street View Found</div>');
            }
        };
        // Use streetview service to get the closest streetview image within
        // 50 meters of the markers position
        streetViewService.getPanoramaByLocation(marker.position,
            radius, getStreetView);
        // Open the infowindow on the correct marker.
        infowindow.open(map, marker);
    }
}
  • I really REALLY wish college would seriously start teaching asynchronous programming. Does not matter if it's Javascript or C++ (yes, there are C++ async frameworks: check out GTK) or Java or Tcl. I'm tired of this question – slebetman Jul 08 '17 at 03:58
  • Grechko, don't be offended by slebetman's comment. The statement "I'm tired of this question" is not directed at you personally! It's just that variations of this same question have been asked so many times by so many people. Even the most helpful person may once in a while say "Oh no, not _this_ question again!" :-) – Michael Geary Jul 08 '17 at 05:46
  • I looked at the answers to the linked "duplicate" question, and almost all of them are _terrible_. They go into so much detail about Angular and Promises and all that stuff that it will make your head spin! The basic point you need to learn is really very simple: it's useless to return any value from the `$.getJSON()` callback with the idea that your other code could use that value. That's not how async callbacks work. Instead, what you need to do is simply do all of the processing of the JSON data _inside_ the callback, or in another function that you call _from_ the callback. – Michael Geary Jul 08 '17 at 05:49
  • To understand _why_ you need to do it that way (process the data in the callback or in a function called from the callback), you can pepper your code with `console.log()` calls (with a different message in each one). Put one _before_ the `$.getJSON()` call, one _inside_ the callback, and another one _after_ the end of the callback. Then run your code and note the order of the log messages. You'll find that the one _inside_ the callback runs _last_. In other words, `$.getJSON()` returns and the next line runs _before the data is ready_. Inside the callback is where the data is available. – Michael Geary Jul 08 '17 at 05:55
  • @Michael Geary, thank you so much for your advice - after few days of learning about this topic and examining my own code with console.log, I was able to understand how the concept works! The final struggle I am left with is finding out how to data-bind the results of curWeather, instead of using jQuery or Javascript DOM methods. I know that should use text data-binding, but I am getting "Uncaught TypeError: viewModel.curWeather is not a function" all the time. Do you know any resources besides knockout documentation, where I can learn more about it? – Grechko Dmitry Jul 09 '17 at 19:11

2 Answers2

2

You cannot return object like this as it is callback function. You can write you logic inside the callback function.

function getWeather() {
var city = $("#txtCity").val();
    var weatherAPIXU = "http://api.apixu.com/v1/current.json?key=XXXXXXX&q=" + city;
    $.getJSON(weatherAPIXU, function(data) {
        var forecast = data.current.temp_c;
        var curWeather = forecast + '° C';
        $("#lblTemp").html(curWeather)

    });

The Other option which is not recommended is declare a global variable and assign the return value to the global variable.

var CurrentWeather = null
function getWeather(city) {

var weatherAPIXU = "http://api.apixu.com/v1/current.json?key=XXXXX&q=" + city;
$.getJSON(weatherAPIXU, function(data) {
    var forecast = data.current.temp_c;
    var curWeather = forecast + '° C';
    $("#lblTemp").html(curWeather)
     CurrentWeather = curWeather;
});
  • The global variable you mention in the second option is, as you suggest, a terrible idea and should not be used. In your first option (the correct one), you should remove the `return curWeather` statement from the code because it serves no purpose. – Michael Geary Jul 08 '17 at 05:43
  • Yes totally agree with you. That's why I have mentioned it's not recommended. – Partha Thakura Jul 08 '17 at 05:55
  • Roger that! But please do remove the `return curWeather` from the first example, as it is misleading to have it there. – Michael Geary Jul 08 '17 at 05:57
  • Thanks Michael Geary. I have missed that. Sorry. – Partha Thakura Jul 08 '17 at 06:02
  • No worries, and no apology needed! We are all here to help each other out. BTW, your answer is _much_ better than the highly-voted answers on the linked "duplicate" question. You should take a look at them - they make it seem so complicated! Angular, Promises, and thousands of words of explanation. The truth is that there is just one simple little fact people need to understand, which you explained in your first option: you must process the data _inside_ the async callback (or in a function that it calls, or in something equivalent like a Promise callback). That's the key point. – Michael Geary Jul 08 '17 at 06:12
0

Your function probably returns a value, my guess would be that you are trying to use the value before the function returns.

Hastradamus
  • 121
  • 8