Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Javascript passing parameters to a setTimeout function inside of a loop

I'm trying to execute a setTimeout() function inside of a for loop in Javascript, but I am getting the error "shape is undefined", even though it is defined, and I'm passing that as a parameter in the function within the setTimeout() call. The function works just fine if I delete the setTimeout enclosure.

Why am I getting this error and how can I fix it?

Thanks!

function fadeShapes(layer, movement, opacity, speed) {
    var shapes = layer.getChildren();

    for(var n = 0; n < shapes.length; n++) {
        var shape = layer.getChildren()[n];
        setTimeout(function(shape){
            shape.transitionTo({
                alpha: opacity,
                duration: speed
            }); 
        }, 100);                
    }
}
like image 696
HandiworkNYC.com Avatar asked Dec 12 '22 23:12

HandiworkNYC.com


2 Answers

JavaScript does not have block scope so all of your timeout functions are pointing at the same variable shape, which after the loop finishes points to an undefined index of your array. You can use an anonymous function to emulate the scope you're looking for:

for(var n = 0; n < shapes.length; n++) {
    var shape = shapes[n]; //Use shapes so you aren't invoking the function over and over
    setTimeout((function(s){
        return function() { //rename shape to s in the new scope.
            s.transitionTo({
                alpha: opacity,
                duration: speed
            });
        };
    })(shape), 100);   
}

As you could tell by my issues getting the brackets matched up, this can be a little tricky. This can be cleaned up with ES5's Array.forEach:

layer.getChildren().forEach(function(shape) { //each element of the array gets passed individually to the function
    setTimeout(function(shape){
        shape.transitionTo({
            alpha: opacity,
            duration: speed
        }); 
    }, 100);                
});

forEach is built into modern browsers but can be shimmed in Internet Explorer older browsers.

like image 177
Dennis Avatar answered Feb 02 '23 00:02

Dennis


This is a common closure problem, here is the fixed code:

function fadeShapes(layer, movement, opacity, speed) {
    var shapes = layer.getChildren();

    for(var n = 0; n < shapes.length; n++) {
        var shape = layer.getChildren()[n];
        setTimeout((function (bound_shape) {
            return function() {  // return function!
                bound_shape.transitionTo({
                    alpha: opacity,
                    duration: speed
                }); 
            };
        }(shape)) // immediate execution and binding
        , 100);                
    }
}

What happens in your code is that the for loop will run and n functions will be scheduled from execution in 100ms, but the value of shape changes! So when your callback gets called shape is the value of shapes[length-1] (the last shape).

To fix it, you must 'close' the value using a closure. In this case a function that binds on the value of shape and returns the function you want executed in 100ms.

like image 42
Halcyon Avatar answered Feb 02 '23 01:02

Halcyon