Reputation: 13771
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?
Upvotes: 0
Views: 1467
Reputation: 46323
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.
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.
Upvotes: 1
Reputation: 4121
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();
}
});
}
Upvotes: 1
Reputation: 2608
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.
Upvotes: 1