0

I've been programming for a few years but I'm new to JavaScript. I'm trying to create a hacky version of a standard thread 'notify' function. I'm running a series of ajax data requests that each take about 200 milliseconds to execute, and I need to wait for them ALL to finish before performing an action (drawing a graph).

My solution was to have each thread increment a global variable when it finished. I then created a timer using setTimeout to check every 100 milliseconds to see if all threads had updated the variable, and if they had, to perform the action.

Despite being a bit hacky, this worked.

Fast-forward a few weeks into the future and my program is growing, and we now need to be able to have several graphs on the same page, each of which manages its data independently using the aforementioned ajax requests, as well as having several different pages that use the graphs. So I extract the graphing code into a module with require.js, and define a class that performs the graphing to isolate each graph from the others, but suddenly setInterval doesn't work. I do some Googling and find the following article, which I sort of understand...

http://www.novogeek.com/post/2010/02/08/Scope-problems-with-JavaScript-setInterval-setTimeout-Use-closures!.aspx

So anyway it seems that 'this' becomes the window for some strange reason when setInterval is used, and therefore it can't see my methods.

Anyway, I tried creating a var inst to contain 'this' explicitly, which seemed to help, but for some reason it doesn't like passing 'inst' as an argument in the recursive call. The error message from the Firebug console is this:

missing ] after element list [Break On This Error]
}(9.9, [object Object]))

Here's a sample from my code:

var inst = this;

// some code... then in a function...
function (){
    inst.waitUntil(10, inst);
}

inst.waitUntil = function(how_many_seconds, inst){
    if (how_many_seconds < 0){
        alert("Script timed out.");
        return;
    } else {
        if (inst.allSchoolsLoaded()){
            setTimeout("("+inst.draw+"("+inst.after+"))", 100);
        } else {
            var argString = "("+inst.waitUntil+"("+(how_many_seconds-0.1)+", "+inst+"))";
            //alert(argString);
            setTimeout(argString, 100);
        }
    }
}

You can assume all the variables mentioned are defined earlier in the class.

I'm at my wits end here, any help would be really appreciated. If anyone can suggest a better solution for the threading issue that avoids setInterval entirely, that would be awesome, otherwise if you can propose a way to get setInterval to work or an alternative function that would be great too.

jQuery is available, and I don't mind installing other tools if they will help.

Many thanks in advance,

Alex

Alex
  • 18,332
  • 10
  • 49
  • 53
  • So what's wrong with `var completedCalls = 0; function ajaxThreadResult(){ ... completedCalls++; }` then check if `completedCalls == callsMade`? – Brad Christie Feb 24 '12 at 01:24
  • see this question and replies there: http://stackoverflow.com/questions/2130241/pass-correct-this-context-to-settimeout-callback/9298306#9298306 – Misha Reyzlin Feb 24 '12 at 01:24
  • Yes, that's actually what I'm doing.... the function allSchoolsLoaded() just tests completedCalls == callsMade (with different variable names, obviously). The issue was how to check that status at intervals... – Alex Feb 24 '12 at 06:27

3 Answers3

3

You are trying to create a string with an object. The standard string representation of an object is [object Object]. You probably will also have problems with inst.waitUntil, as it is converted to the string representation of the function (which is the function's source in some (most?) browsers).

Instead of creating a string, just pass a function to setTimeout:

setTimeout(function() {
    inst.waitUntil(how_many_seconds-0.1, inst);
}, 100);

Now, inside waitUntil, this will refer to inst. Given this, you might be able to simplify your code further.

For example:

this.waitUntil = function(how_many_seconds){
    var self = this;
    if (how_many_seconds < 0){
        alert("Script timed out.");
        return;
    } else {
        var callback = function() {
            self.waitUntil(how_many_seconds-0.1);
        };

        if (this.allSchoolsLoaded()){
            callback = function() {
                self.draw(self.after);
            };
        }
        setTimeout(callback, 100);
    }
};
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • :-) we wrote the same stuff, sorry, but I like having only one setTimeout more, nice! – Misha Reyzlin Feb 24 '12 at 01:38
  • I remember looking for examples on the internet for setTimeout and for some reason they didn't show that you could pass a function, so I made the same mistake as Alex. Wish I had known about Stack Overflow back then to get an answer like this one. ;-) – Greg Pettit Feb 24 '12 at 01:47
  • Right, I think I got confused and thought that setInterval always sets 'this' to 'window'... Thanks so much for your help :) – Alex Feb 24 '12 at 06:25
  • One more thing - your solution worked perfectly - could you just explain to me why it does work with self=this, but doesn't work if 'this' is used without assignment to 'self'? Thanks again, you've emancipated me from a javascript nightmare... :) – Alex Feb 24 '12 at 06:47
  • @Alex: `this` is very special, [MDN has a great page about it](https://developer.mozilla.org/en/JavaScript/Reference/Operators/this), explaining how it works. If you call a function normally, such as `foo()`, then `this` usually refers to the global object, i.e. `window`. Internally, `setTimeout` or `setIntervall` will just call the function in that way, so inside the callback, `this` refers to `window`. – Felix Kling Feb 24 '12 at 09:42
1

Don't use string, it's using eval internally and it's bad for performance and maintainability of your code. Instead of this:

if (inst.allSchoolsLoaded()){
    setTimeout("("+inst.draw+"("+inst.after+"))", 100);
} else {
    var argString = "("+inst.waitUntil+"("+(how_many_seconds-0.1)+", "+inst+"))";
    //alert(argString);
    setTimeout(argString, 100);
}

Do this:

if (inst.allSchoolsLoaded()){
    setTimeout(function () {
      inst.draw(inst.after);
    }, 100);
} else {
    setTimeout(function () {
      inst.waitUntil(how_many_seconds - 0.1);
    }, 100);
}

Also, you don't need to pass inst if you are using waitUntil method on instance, this context will be set to inst itself, so:

inst.waitUntil = function (seconds) {
    // `this` is now pointing at `inst` 
    // we will cache a reference to the object into a variable
    // because anonymous function will have its own context (`this`)
    // in the link provided in comment you can see alternatives such as using 
    // ECMAScript `bind` Function method to bind function context to certain object
    var self = this;
    if ( seconds < 0 ) return alert('timeout');
    if ( inst.allSchoolsLoaded() ){
        setTimeout(function () {
            self.draw(self.after);
        }, 100);
    } else {
        setTimeout(function () {
            self.waitUntil(seconds - 0.1);
        }, 100);
    }  
};
Misha Reyzlin
  • 13,736
  • 4
  • 54
  • 63
0

The solutions posted here worked perfectly, thanks for your help.

In case anyone else has a similar need to perform multiple AJAX requests, I actually stumbled upon a jQuery method the handles this elegantly:

http://api.jquery.com/jQuery.when/

It can take several 'Deferred' objects, including ajax requests, and waits for all of them to execute successfully or one of them to fail. Using this, I can get rid of my setInterval timer entirely.

Cheers,

Alex

Alex
  • 18,332
  • 10
  • 49
  • 53