Adrian Moisa
Adrian Moisa

Reputation: 4353

How to stop the previous instances of the same function if it's called multiple times?

I have written a custom animation function. It usually works just fine, but when I call animate(); in rapid succession with different endCallbacks, sometimes the callbacks overlap really badly, causing the wrong action at the wrong time.

The problem is that the function instantiates multiple times and executes untill the endValue is reached. The currentValue is changed so fast that I get to see just the last value in my html page animation. This hiddes this unwanted behavior.

What I need when I call animate(); a second time is to end the first instance of animate(); and trigger a new one with new values and a new callback. Also at the same time I want to stop the setTimeout() function just to make sure no wrong callback is triggered.

window.onload = function(){
    document.addEventListener('click', // some button
        function (){
            animate(1, 10);
        }, false
    );
}

function animate(startValue, endValue, callback, endCallback) {
    var startValue = startValue,
        currentValue = startValue,
        endValue = endValue,
        callback = callback,
        timeout = null;

    loopAnimation();
    function loopAnimation(){
        if (currentValue != endValue){
            timeout = setTimeout(function(){
                currentValue++;

                // Callback executes some page manipulation code
                if (typeof callback !== "undefined") callback(currentValue); 
                console.log(currentValue);
                loopAnimation();
            },500)
        } else {
            console.log("This callback triggers some specific changes in my page");
            if (typeof endCallback !== "undefined") endCallback();
        }
    }
}

Instead of seeing in the console: 1,2,3, - 1,4,2,5 ... 6,9,7,10,8,9,10

I'd like to see just: 1,2,3, - 1,2 ... 7,8,9,10

However, keep in mind that because of the way I use animate() in my script I can't relly on knowing the name or scope of the input variables. This cuts me from being able to solve it myself.

Upvotes: 1

Views: 2148

Answers (2)

GameAlchemist
GameAlchemist

Reputation: 19294

What you need is to store the last timeout id you used. So next time you start a new animation, you clear any ongoing animation using this timeout id and clearTimeout.
I found convenient to store the interval on the function itself.

See the jsbin here : http://jsbin.com/nadawezete/1/edit?js,console,output

window.onload = function(){
    document.addEventListener('click', // some button
        function (){
            animate(1, 10);
        }, false
    );
};

function animate(startValue, endValue, callback, endCallback) {   
    var currentValue = startValue;
    if (animate.timeout) clearTimeout(animate.timeout);
    loopAnimation();
    function loopAnimation(){
        if (currentValue != endValue){
            animate.timeout = setTimeout(function(){
                console.log(currentValue);
                currentValue++;    
                // Callback executes some page manipulation code
                if (callback ) callback(currentValue); 
                loopAnimation();
            },500);
        } else {
            console.log("This callback triggers some specific changes in my page");
            if (endCallback) endCallback();
        }
    }
}

Upvotes: 1

jrochkind
jrochkind

Reputation: 23317

While it isn't quite the implementation you're asking for, I wonder if Underscore's throttle or debounce would meet the need?

debounce will make sure your function is called no more than X times per second -- it'll still be executed once per every time called, but the subsequent calls will be delayed to meet your rate limit. So if you called animate twice in quick succession, debounce can delay the second execution until 100ms after the first or what have you.

throttle will basically ignore calls that occur during the rate limit. So if you call your animate 10 times within 100ms, you could have it throw out all but the first. (Actually, it'll do the first one, plus one at at the end of the wait period).

You don't need to use all of underscore to get these methods; I've seen people frequently copy and pasting just the debounce and/or throttle functions from underscore. If you google, you can find some standalone throttle or debounce implementations.

Throttle and debounce are commonly used in just your case, animation.

For your original spec, to actually "end the first instance of animate()" -- there's no great reliable way to do that in javascript. There's no real general purpose way to 'cancel' a function already being executed. If you can make it work with debounce or throttle, I think it will lead to less frustration.

Upvotes: 2

Related Questions