22

I have a jsFiddle up with an example to work from.

$().ready(function() {
    $('.test').each(function() {
        $this = $(this);
        $this.data('Worker', function() {
            $('#stop').html((parseInt($('#stop').html()) + 1))
        })
        setInterval($this.data('Worker'), 100);
    });

    $('#stop').click(function() {
        // I want the worker function to stop being triggered here
        $('.test').remove();
    });
});

What I would like to do is attach a worker function to an element in the DOM so that when the element is removed, the worker function stops.

Is something like this possible?

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
Justin808
  • 20,859
  • 46
  • 160
  • 265
  • Are you saying you want to sort of attach a listener to the divs that triggers onremove and stops the set interval? So regardless of how it gets removed, the fact that it's removed will stop the interavl? – James Montagne Apr 22 '11 at 23:31

8 Answers8

35

Late to the party, but I solved this way:

var id = setInterval(function()
{
    if(some_condition)
    {
        clearInterval(id);
    }
}, 1000);

Definitely the simplest way out of all: no unnecessary storing, no tricks.

Andrius Naruševičius
  • 8,348
  • 7
  • 49
  • 78
17

Here you go:

var stopBtn = $( '#stop' );

$( '.test' ).each(function ( i, elem ) {
    var tid = setInterval(function () {
        if ( elem.parentNode ) {
            stopBtn.html(function ( i, num ) { return parseInt(num,10) + 1; });
        } else {
            clearInterval( tid );
        }
    }, 1000);
});

stopBtn.click(function () {
    $( '.test' ).remove();
});

Explanation:

First of all we need a closure - the interval function needs to know which test-DIV is its "owner", ergo, we need to pass a reference to the DIV into the interval function. In my code, that reference is stored in the elem variable, which is available inside the interval function because of closure. So, the interval function checks whether or not its "owner" DIV is still attached to the DOM (by examining its parent node). If it is, the stop-button is incremented. If it's not, the interval is cleared. However, in order to be able to clear the interval, we require its timer ID. We have this ID, because we stored it inside the "tid" variable (the setInterval call returns the timer ID).

Live demo: http://jsfiddle.net/simevidas/ZjY7k/15/

Šime Vidas
  • 182,163
  • 62
  • 281
  • 385
  • @Justin I've added an explanation (just in case `:)`). – Šime Vidas Apr 23 '11 at 00:27
  • @Šime this actually won't stop the interval, it will continue to execute, it just won't update the stop button since elem.parentNode is now null. After removing the element the data is gone, so it can't get the interval ID to stop it. You could probably modify it to keep a global list of elementID->intervalID and then stop the interval related to the element assuming all elements have an ID. – James Montagne Apr 23 '11 at 21:27
  • Something like this http://jsfiddle.net/ZjY7k/13/ could probably be cleaned up slightly. Stick an alert into the setinterval and you will see the original continues to execute every .5 second. – James Montagne Apr 23 '11 at 21:34
  • @king As for now, I'm storing the timer ID inside a custom `tid` property of the DIV DOM object. It's not an ideal solution... – Šime Vidas Apr 23 '11 at 22:27
  • Someone bumped this question to the front page, and I couldn't resist contributing some evil: you can store the `tid` in a local variable in the closure instead of the element. This will also prevent accidental clobbering of the `tid` attribute. (See: http://jsfiddle.net/millimoose/ZjY7k/24/ ) – millimoose Oct 03 '12 at 21:19
  • @millimoose Yes, that's certainly a better approach. I'll update my question. – Šime Vidas Oct 03 '12 at 21:35
  • @ŠimeVidas Broken fiddle – Zach Saucier Dec 29 '13 at 19:51
  • @ZachSaucier I thought jsFiddle demos live forever!? – Šime Vidas Dec 29 '13 at 19:55
  • @ŠimeVidas Perhaps you deleted it unknowingly? – Zach Saucier Dec 29 '13 at 19:56
3

Another way that you can remove intervals, is using the clearInterval inside the interval.

window.setInterval(function(){
    // Do all your stuff here
    if(/*something something*/){
        window.clearInterval(this);
        alert("The interval didn't clear");
    }
}, /*However long*/);

If the interval doesn't clear, it will show an alert saying, "The interval didn't clear". Otherwise, the interval sucessfully cleared.

Seize
  • 59
  • 7
  • If you put 4 spaces before each line (and more on the lines that should be indented) then you will surround all of your code with the gray code highlight background. This looks a little better than the `` characters, which are usually used to highlight code within a line like this: `codeHere()`. Hope this helps! – Matt C Apr 29 '16 at 01:20
  • 1
    Thank you Matthew for the advice. This will help me a lot with my answers. – Seize Apr 29 '16 at 01:54
1

I suspect that the proper way to implement a "self-terminating" interval is to implement the interval via a series of timeouts.

In semi-pseudocode:

setTimeout(function repeat() {
  if (continue_to_run) // this code here may cause timing to be inaccurate
    setTimeout(repeat, interval_duration);
  do_task_on_interval();
}, 0)

This repeating timer cleans up after itself.

Steven Lu
  • 41,389
  • 58
  • 210
  • 364
1

Maybe it's too late but here's my contribution:

/**
 * This function sleep each intervalMs until filter is true, when filter is true this function 
 * resolve promise and clean it self.
 * @param {*} intervalMs
 * @param {*} filter
 * @returns Promise<Void>
 */
const sleepUntil = (intervalMs, filter) => {
    let id = null;
    return new Promise((resolve) => {
        id = setInterval(() => {
            if (filter()) { resolve(); }
        }, intervalMs);
    }).then(() => {
        id = clearInterval(id);
    });
};

I use for Mutex behaviour like these:

waitToSyncronize() {
    return sleepUntil(50, () => this.syncronizing === false);
}
Raúl Garcia
  • 282
  • 3
  • 18
0

It's a little-known fact, but setInterval returns a value that you can use with clearInterval to cancel the interval.

var result = setInterval(function, delay);
// at some point later...
clearInterval(result);
zneak
  • 134,922
  • 42
  • 253
  • 328
  • yes, but if you look at my example, i'm creating a unknown number of intervals (based on the number of divs with a given class) that may or may not be stopped at any point (removal of a div). I would like them to stop themselves if at all possible. – Justin808 Apr 22 '11 at 23:13
  • Can't you store them in an array and iterate over? – zneak Apr 22 '11 at 23:14
0

Use clearInterval:

$().ready(function () {
    $('.test').each(function () {
        $this = $(this);
        $this.data('Worker', function () { $('#stop').html((parseInt($('#stop').html()) + 1)) })
        $this.data('intervalId', setInterval($this.data('Worker'), 100));
    });

    $('#stop').click(function () {
        $('.test').each(function() { clearInterval($(this).data('intervalId')); }).remove();
    });
});
Michael Dean
  • 1,506
  • 9
  • 11
  • 1
    This would break the second there is more than one div with a `test` class. Also, its not automated (I explicitly have to call clearInterval) – Justin808 Apr 22 '11 at 23:16
  • I updated the example. I'm not sure what you mean by "automated" - you have to call clearInterval to stop an interval. – Michael Dean Apr 22 '11 at 23:28
  • I guess what I mean by automated is that the line `$('.test').remove();` would stop the interval. I could automate the clearInterval call if there was a jquery `.remove()` callback just like there is a `.load()` call back... I guess I'm looking for another way to automate the clearInterval call so I dont have to explicitly call it myself when I do the `.remove();` – Justin808 Apr 22 '11 at 23:35
  • This might help: http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom – Michael Dean Apr 22 '11 at 23:48
0

Javascrip use a different order of evaluation for assignments. That said, they are evaluated from left to right, meaning you can use the variable inside its expression.

const stop = setInterval(() => {
  // Execute the function body.
  console.log('Executed')
  // When you want to stop it, you
  // can clear the interval with the
  // stop handler.
  clearInterval(stop)
}, 1000)

In that particular example, the interval is executed only once and cleared after 1 second of defining it.

Erik Campobadal
  • 867
  • 9
  • 14