0

I am working on an application where I need to compile a table of weather data which has the weather information for the same day for the last 10 years. I am using Forecast.io which provides an easy API to query old weather data:

https://api.forecast.io/forecast/APIKEY/LATITUDE,LONGITUDE,TIMESTAMP

So basically I have to pass my key, lat, long and timestamp for which I need the data for.

My code look like:

function bindweatherHistoryTable(){
        console.log(historyData);
        var weatherHistoryTable = rivets.bind($('#weatherHistoryTable'), {data: historyData});
    }

    var historyData = {};
    for(i = 1; i <= 10; i++){
        fetchHistoryUrl = '//api.forecast.io/forecast/' + forecastToken + '/' + farmLat + ',' + farmLng + ',' + (moment().subtract(i, 'years').valueOf() / 1000).toFixed(0);

        var promise = $.ajax({
            method: 'GET',
            dataType: 'JSONP',
            url: fetchHistoryUrl,
            success: function(response){
                var data = {
                    temperature: response.currently.temperature,
                    dewPoint: response.currently.dewPoint,
                    humidity: response.currently.humidity,
                    windSpeed: response.currently.windSpeed,
                    windBearing: response.currently.windBearing,
                    pressure: response.currently.pressure
                };

                historyData[moment().year() - i] = data

                console.log(data);
            }
        });

        promise.done(function(){ console.log('hello') });

        if(i == 10){
            console.log(i);
            promise.done(bindweatherHistoryTable());
        }
    }
}

So basically I am using a for loop to query last 10 years and then compile the data into a single object and finally bind the object to the view using Rivets.

The object is being compiled because I am logging every ajax call object that is formed. I am also using the promise.done() method to log 'hello' after every ajax call was a success.

I am also specifying that if it is the last iteration, the promise should log the final history object and bind it to the history table.

Every call is success. I can see the output object for every call in the console. I can also see the 'hello' in the log after every promise is resolved.

But the final object which should be logged after everything is logged in the very beginning before any of the ajax calls are made. I am new to promises and I don't understand why. I just want to take the final object and use it in my view by binding it to my table. What am I doing wrong and what can I do to fix it?

1
  • Since the calls are asynchronous, you aren't guaranteed to get them back in the same order you sent them out. Commented Jul 9, 2015 at 21:05

3 Answers 3

1

You're probably confused about the async part of promises (and async in general).
Your for loop "runs to completion", while the async part (the $.ajax calls) are being processed asynchronously, and their success callbacks are being triggered sometime in the future. So... i is advancing really quickly and getting to 10 before the async calls are done. What you could do instead is call the bind function inside the success callback ONLY once all your requests are complete.

EDIT: Regarding the comment about the wrong key in historyData

A more general approach than relying on the specific case where the data is available in the response object is to pass "context data" into the ajax callback using the context property in the $.ajax configuration parameter. That context is then available as the this object within the callback. Combine that with a "closure" and you can solve that part as well.

Try this:

...
context: {year: i /*other context data can be added here if needed*/},
success: function(response) {
  var data = {
    temperature: response.currently.temperature,
    dewPoint: response.currently.dewPoint,
    humidity: response.currently.humidity,
    windSpeed: response.currently.windSpeed,
    windBearing: response.currently.windBearing,
    pressure: response.currently.pressure
  };

   historyData[moment().year() - this.year] = data;

   if(Object.keys(historyData).length == 10) // 10 properties set in historyData...
     bindweatherHistoryTable();
  }
...

And you don't need to use promise.done at all.

Sign up to request clarification or add additional context in comments.

2 Comments

I used your if statement but I still had a problem with the history data object. You see that I am using i in the key part of history data. Well the async part of promises messed that up too. And i was coming to be 11 every since the loop had already finished processing. So basically I had only one key which was 2004 (2015 - 11). I changed this by using the time returned with the response itself moment(response.currently.time, 'X').year();. Now I had the perfect object with 10 entries and your if statement worked. Please update the answer and I will accept it. :)
@Rohan - I modified the answer to resolve that issue as well, but I took a more general purpose approach when doing so. That's not to say the method you mention isn't good (or even inferior), but the problem can arise in situations where that won't work.
1

Promises are asynchronous, so your for loop will finish before they fulfil. For the same reason, the value of i in your success functions will always be 10. You need to keep track of how many promises have fulfilled and check this each time a promise fulfils. Basic concept:

var complete = 0;
function completeCallback() {
    // All promises done, bind your table, etc
}
for (var i = 0; i < 10; i++) {
    $.ajax(url).done(function () {
        complete ++;
        if (complete == 10) {
            completeCallback();
        }
    }
}

Side note: I think if you're using the promises feature of jQuery ajax, you don't need to specify a success callback.

Comments

1

The problem there is that you are logging i before the ajax calls finish. Don't forget they are asynchronous calls so, for instance, you could have something like:

var promises = 0, length = 10;
for(i = 1; i <= length; i++){
        fetchHistoryUrl = '//api.forecast.io/forecast/' + forecastToken + '/' + farmLat + ',' + farmLng + ',' + (moment().subtract(i, 'years').valueOf() / 1000).toFixed(0);

        var promise = $.ajax({
            method: 'GET',
            dataType: 'JSONP',
            url: fetchHistoryUrl,
            success: function(response){
                var data = {
                    temperature: response.currently.temperature,
                    dewPoint: response.currently.dewPoint,
                    humidity: response.currently.humidity,
                    windSpeed: response.currently.windSpeed,
                    windBearing: response.currently.windBearing,
                    pressure: response.currently.pressure
                };

                historyData[moment().year() - i] = data

                console.log(data);
            }
        });

        promise.done(function(){ 
          promises++;
          console.log(promises);
          if (promises === length) {
            bindweatherHistoryTable();
          }
        });
    }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.