young-ceo
young-ceo

Reputation: 5374

How to stop recursive loop with `requestAnimationFrame`

I have a class with the recursive loop function like the below:

class Foo {
    private isLoopOnFlg: boolean = false;

    public StartLoop() {
        this.isLoopOnFlg = true;

        this.recursiveLoopWithDelay(() => {
            // Modifying DOM objects
        }, 1000);
    }

    public StopLoop() {
        this.isLoopOnFlg = false;
    }

    // THE PROBLEM
    private recursiveLoopWithDelay(loopFn: any, delay: number) {
        let stamp = Date.now();

        // How do I stop the loop using `isLoopOnFlg`?
        function _loop() {
            if (Date.now() - stamp >= delay) {
                loopFn();
                stamp = Date.now();
            }

            window.requestAnimationFrame(_loop);
        }

        window.requestAnimationFrame(_loop);
    }
}

As you see, the function recursiveLoopWithDelay won't be stopped - so I've added a new private variable isLoopOnFlg and I want to use it to stop the recursive function - but I am not sure how to do it. I tried to stop the function by adding a new parameter keepLoopFlg in the function like the below:

...
private recursiveLoopWithDelay(loopFn: any, delay: number) {
    let stamp = Date.now();

    // Added a new param `keepLoopFlg` but it's not changed when `StopLoop()` is called
    function _loop(keepLoopFlg: boolean) {
        if (keepLoopFlg && Date.now() - stamp >= delay) {
            loopFn();
            stamp = Date.now();
        }

        window.requestAnimationFrame(_loop.bind(this, this.isLoopOnFlg)); // Used `.bind(...)`
    }

    window.requestAnimationFrame(_loop.bind(this, this.isLoopOnFlg));
}
...

But the above code won't do what I want - when StopLoop() is called, the recursive function still keep going (memory leak). I'd love to learn how to stop the recursive function with the current structure and to prevent memory leak. Please enlighten me!

Upvotes: 0

Views: 703

Answers (1)

Spidy
Spidy

Reputation: 39992

First, it's not really recursive since it queues the callback on the event loop rather than calling the callback directly. So you don't have to worry about running out of memory on the call stack.

To stop calling requestAnimationFrame, you simply don't call it. The question is, when do you want to stop calling it? If you are providing a utility function for someone else, you usually let them dictate when to "unsubscribe" or stop the updates.

private recursiveLoopWithDelay(loopFn: any, delay: number) {
    const self = this;
    let stamp = Date.now();

    function _loop() {
        // If we aren't looping anymore, just exit the code.
        // Don't requeue requestAnimationFrame
        if (!self.isLoopOn) {
            return;
        }

        if (Date.now() - stamp >= delay) {
            loopFn();
            stamp = Date.now();
        }

        window.requestAnimationFrame(_loop);
    }

    window.requestAnimationFrame(_loop);
}

You can also skip binding by using Lexical scope like I've done here. Storing this in a variable self that I can lookup at any point.

Upvotes: 2

Related Questions