0

This is quite a difficult problem to explain, but it probably comes from a misunderstanding of how javascript works with objects:

I'm trying to build a list of points over a period of several years: I have two date objects of which the year is incremented in each cycle.

I'm calling a function getPoint that builds a point using the date objects, here's the code:

while(td2.getTime()<date2.getTime())
{
  td1.setYear(td1.getFullYear()+1);
  td2.setYear(td2.getFullYear()+1);
  console.log(td1);
  test=getPoint(sessionStorage.Link,stat,td1.getFullYear()+'-'+td1.getMonth()+1+'-     '+td1.getDate(),td2.getFullYear()+'-'+td2.getMonth()+1+'-'+td2.getDate());
  $.extend(points,test);
}

The getPoint function is this:

  function getPoint(who,stat,date1,date2){
  string=date1+"/"+date2;

 var point={};
  $.ajax({
    type:'GET',
   url: who +'/'+ stat + '/'+date1+'&'+date2,
   data:{"token" : member.token},
   dataType:'JSON',
    error: function(){
    point={};

   },
   success:function(data){
    stat=stat.split('/');
    stat=stat[1];
    point[string]=data[stat];
   }
  });
  return point;
 }

At the end of the while loop I'm expecting points to ressemble:

points={date1/date2: value, date1+(1year)/date2+(1year) : value, ..., date1+(n years)/date2+(n years) : value}

Unfortunately I only get:

points = {date1/date2 : value, date1+nyears/date2+nyears : value} 

Where n is the number of iterations.

To try and understand the problem, I've made this appear in the console and it appears to me that the function getPoint is only getting called using the date object in it's state after the while loop has finished. Can anyone explain why this might be the case, if it is possible for it to be the case?

Edit I tried to replace my code with the following, but I still only get:

[Log] Object (mystats.js, line 61)
 2015-2016: 0
 __proto__: Object

in the log.

function getPoint(who,stat,date1,date2){

 return $.ajax({
  type:'GET',
  url: who +'/'+ stat + '/'+date1+'&'+date2,
  data:{"token" : member.token},
  dataType:'JSON',
 });

}

var td1=new Date(date1.getTime());
 var td2=new Date(date1.getTime());
 var points={};
 td2.setFullYear(td2.getFullYear()+1);
 getPoint(sessionStorage.Link,stat,td1.getFullYear()+'-'+(td1.getMonth()+1)+'-'+td1.getDate(),td2.getFullYear()+'-'+(td2.getMonth()+1)+'-'+td2.getDate()).done(function(result)
   {
  tStat=stat.split('/');
   points[td1.getFullYear()+'-'+td2.getFullYear()]=result[tStat[1]];

   });
 while(td2.getTime()<date2.getTime())
  {
  td1.setYear(td1.getFullYear()+1);
  td2.setYear(td2.getFullYear()+1);
  getPoint(sessionStorage.Link,stat,td1.getFullYear()+'-'+(td1.getMonth()+1)+'-'+td1.getDate(),td2.getFullYear()+'-'+(td2.getMonth()+1)+'-'+td2.getDate()).done(function(result){
   tStat=stat.split('/');
   var newPoint={};
   newPoint[td1.getFullYear()+'-'+td2.getFullYear()]=result[tStat[1]];
   $.extend(points,newPoint);
  });

  }

Update : This is because the date objects have changed before the callback is called.. my question is therefore how can I make sure that the callBack uses the object in it's state when the original AJAX call is called?

Jack
  • 247
  • 3
  • 10
  • How have you created the td2 and date2 objects in the first place? – thetrompf Mar 23 '14 at 17:07
  • 1
    No one can explain why that *is* the case, because the reality is that it certainly is *not* the case. `getPoint` *is* called during every pass through the loop, using the values that exist during that pass. If you post more information about why you think otherwise, we can try to clarify whatever is confusing you. – ruakh Mar 23 '14 at 17:08
  • 1
    @ruakh, i'll post the console.log results that lead me to believe that this is the case – Jack Mar 23 '14 at 17:10
  • @Jack it's better to post a jsfiddle (see jsfiddle.net if you don't already know what it is) – markasoftware Mar 23 '14 at 17:10
  • @Jack: Are you basing this problem on the console output, or on the behavior of the application? I have a feeling that you're assuming it's a bug before you've actually confirmed that it is. – cookie monster Mar 23 '14 at 17:10
  • 1
    @Markasoftware: No, a jsfiddle is *not* better than having information directly in the question. Off site info is fine as long as it's merely supplemental. – cookie monster Mar 23 '14 at 17:12
  • I'm basing the problem on the console output of $.extends(points,test), which should contain as many objects as the number of times I called test (because the function returns a unique version of test every time), but only contains 1. The problem seems to come from the getPoint function because, when I replace test with an object of this form {testi:0} (where i is incremented) I get the expected result – Jack Mar 23 '14 at 17:20
  • @Jack: Looks like the same issue as I describe in my answer below. Just using a different object. You don't show your `console.log()` call, but seems that you're constantly modifying the object in the loop, and assuming the log will show its current state. Instead the console would just show the latest version of the object. To capture its in-loop state, you'd need some serialization of it. – cookie monster Mar 23 '14 at 17:30
  • @thetrompf, I misread your question, I create td2 and date2 like this: var date2= $('#popupMenu form input[name="date2"]').val(); date2=date2.split('/'); date2=new Date(date2[2],date2[1]-1,date2[0]); and var td2=new Date(date1.getTime()); date1 is created in an analogous way to date2. – Jack Mar 23 '14 at 17:39
  • possible duplicate of [How to return the response from an AJAX call?](http://stackoverflow.com/questions/14220321/how-to-return-the-response-from-an-ajax-call) – cookie monster Mar 23 '14 at 17:59
  • I don't think so, the ajax call is working correctly and returns what I expect. Outside the loop the getPoint function returns the correct result. – Jack Mar 23 '14 at 18:03
  • 1
    ah I see, you mean that as the calls are asynchronous, and the date objects are changing, when the ajax is actually executed the date has changed ? – Jack Mar 23 '14 at 18:15
  • That's correct. The object isn't updated until the callback executes, and that's long after the function has returned. – cookie monster Mar 23 '14 at 18:28
  • so I'm only passing references of the objects attributes to the function and not their values – Jack Mar 23 '14 at 18:32
  • I don't see how you can be getting anything but an empty object ({}) returned from getPoint() (since it will return {} before the ajax call completes). – 76484 Mar 23 '14 at 18:53
  • @76484 that is a good point, I don't actually understand that either. – Jack Mar 23 '14 at 20:23
  • Also, I think you need to wrap brackets around 'date.getMonth()+1' or else they (getMonth() and 1) will be concatenated as strings, rather than summed as integers. So if getMonth() returns 2, you'll get 21 instead of 3. – 76484 Mar 23 '14 at 20:41
  • Ok I think you're right there. I've tried to implement a callback function, but now I don't get any result, could you perhaps help me understand this? – Jack Mar 23 '14 at 20:48
  • Certainly. What does your callback function look like? – 76484 Mar 23 '14 at 20:59
  • perhaps you could start a chat to avoid the number of comments on this thread exploding? – Jack Mar 23 '14 at 21:01
  • OK. I think I have done that: http://chat.stackoverflow.com/rooms/50223/javascript-function-in-a-while-loop – 76484 Mar 23 '14 at 21:09
  • i quite naively thought that replacing the code in success: with a function like this would work: function callBack(stat,date1,date2,result){var point = {}; stat=stat.split('/'); stat=stat[1]; point[string]=result[stat]; } ; So that in success I would have: success: function(result) { callBack(stat,date1,date2,result);} and I would pass the callback as a parameter to getPoint. This however does not work – Jack Mar 23 '14 at 21:11
  • turns out I haven't actually got enough rep to use the chat feature – Jack Mar 23 '14 at 21:13
  • I don't think that changes anything. Success is already a callback. The problem is the getPoint() returns an empty object, {}. So you can't extend points with the result of getPoint(). Instead, you need to move the extend call into success, after 'point[string]=data[stat];'. The next problem will be in knowing when all of your ajax calls are completed (ie., when 'points' is ready). – 76484 Mar 23 '14 at 21:18
  • I've edited my original post to include what I've tried, but I'm still not managing to get the correct result – Jack Mar 23 '14 at 21:46
  • You won't get the right result because the values of td1 and td2 will have changed by the time the .done callback is executed. – 76484 Mar 23 '14 at 22:27

1 Answers1

0

You will need to create new date objects because when the .done callback is called, td1 and td2 will not hold the same values they did when the ajax call was dispatched. I think you will have to create a closure in which you create two new date variables:

(function () {
    var innerTd1 = new Date(td1.getTime());
    var innerTd2 = new Date(td2.getTime());
    getPoint(sessionStorage.Link, stat, innerTd1.getFullYear()+'-'+(innerTd1.getMonth()+1)+'-'+innerTd1.getDate(),innerTd2.getFullYear()+'-'+(innerTd2.getMonth()+1)+'-'+innerTd2.getDate()).done(function (result) {
        tStat=stat.split('/');
        //var newPoint={};
        //newPoint[td1.getFullYear()+'-'+td2.getFullYear()]=result[tStat[1]];
        //$.extend(points,newPoint);
        points[innerTd1.getFullYear()+'-'+innerTd2.getFullYear()] = result[tStat[1]];
    });
}());

It will not change the end result, but I have commented out in the done handler the creation of newPoint and the the $.extend call and replaced them with a simple assignment to points because I think this is more efficient.

Remember, if you simply try to console.log(points) right after your while loop, you will most likely not have all the points you expect - because not all the ajax calls will have completed. You may need to do something like this to give the ajax calls enough time to return:

console.log('displaying points in 10 seconds:');
setTimeout(function () {
    console.log(points);
}, 10000);
76484
  • 8,498
  • 3
  • 19
  • 30