Jeremy Harris
Jeremy Harris

Reputation: 24579

Javascript Restart Timer From Within Closure

I'm trying to figure out how I can reset a timer created inside of an immediately invoking function from within the setTimeout closure. Here is my function:

var triggerHeightRecalc = function() {
    setTimeout(function() {
        if(imagesLoaded()) {
            adjustHeight();
        } else {
            triggerHeightRecalc();
        }
    }, 100);
}();

In the event that imagesLoaded() returns false, I receive the following error from attempting to call triggerHeightRecalc():

Uncaught TypeError: undefined is not a function

So I'm not sure if the issue is the function is not in the scope, or maybe it just cannot call itself? I've tried passing triggerHeightRecalc as a parameter in the setTimeout closure, but that doesn't seem to work either.

I've also tried this after reading this SO question:

var triggerHeightRecalc = function() {
    var that = this;
    var callback = function() {
        if(imagesLoaded()) {
            adjustHeight();
        } else {
            that.triggerHeightRecalc();
        }
     };
     timeDelay = window.setTimeout(callback, 100);
}();

What am I doing wrong here, or is there a better way? Is this something that should be a setInterval() instead and I clear the interval when images are loaded?

Side Note: I'm calculating the height of a div inside a jQuery plugin, but I need to wait until the images are loaded in order to get the correct height (not sure if that is relevant).

Upvotes: 0

Views: 147

Answers (3)

mcfedr
mcfedr

Reputation: 7975

Patrick Evans has the right reasons, but there is a neater way to solve it :)

(function triggerHeightRecalc() {
    setTimeout(function() {
        if(imagesLoaded()) {
            adjustHeight();
        } else {
            triggerHeightRecalc();
        }
    }, 100);
})();

Here you are give an internal name to the (still) anonymous function. The name is only visible from within the function itself, its not visible in the global scope. Its called a Named function expression.

Upvotes: 0

gkiely
gkiely

Reputation: 3007

Already answered, but I'll put this in.

First of all, if you just want to wait until all images have loaded you can use: https://github.com/desandro/imagesloaded and then run the above code.

If that's not what you want, and you you just want a function that your setTimeout can run, then you can remove the () at the end of the function.


Here is what's happening in your current code

Your function is missing the opening bracket or similar character !+( (function.

Also your IIFE has no return keyword, and will return undefined to triggerHeightCalc.

If you do want an IIFE then you can either have a private version that is only callable within itself.

(function myModule(){
    myModule(); //calls itself
})();

Or a public version that can be called both inside and outside.

var myModule = (function(){
   return function myMod(){
     myMod();
   }
})();

myModule();

Upvotes: 1

Patrick Evans
Patrick Evans

Reputation: 42736

Since you are invoking the function right from the declaration triggerHeightRecalc is getting set to the return of that function call, which is undefined since you in fact do not return anything.

You can do two things

1. Declare then invoke

var triggerHeightRecalc = function() {
    setTimeout(function() {
        if(imagesLoaded()) {
            adjustHeight();
        } else {
            triggerHeightRecalc();
        }
    }, 100);
};
triggerHeightRecalc();

2. Wrap the declaration in () and invoke

var triggerHeightRecalc;
(triggerHeightRecalc = function() {
    setTimeout(function() {
        if(imagesLoaded()) {
            adjustHeight();
        } else {
            triggerHeightRecalc();
        }
    }, 100);
})();

The second one will create a global variable unless you do the var triggerHeightRecalc; before hand.

Upvotes: 2

Related Questions