Search code examples
javascriptasynchronoussettimeoutcancellationcleartimeout

Best way Handle cancellation of JavaScript function execution


I have three functions:

let timeVar;
let savedFunctionCode;

function startTimer(code) {
savedFunctionCode = code;
timeVar = setTimeout(savedFunctionCode, 2000);
}

function resumeTimer(intervalTime = 2000) {
setTimeout(savedFunctionCode, intervalTime);
}

function stopTimer() {
clearTimeout(timeVar);
}

The sole purpose of this is basically a way to pause and resume specific code.

Now after the timer is instantiated (by calling startTimer) there is the ability to pause or resume the code.

The intended behaviour of pausing and resuming code execution works IF the methods stopTimer() and resumeTimer() are called at least two seconds apart from each other.

However, if resumeTimer() and stopTimer() in that order are called less than two seconds of each other, stopTimer() will not successfully stop/pause resumeTimer() anticipated execution.

My theory:

In a paused state, when resumeTimer is being called, the code gets executed in two seconds. However if you quickly execute the stopTimer() there is essentially nothing to stop because the code execution from resumeTimer has not started yet. (Less than two seconds).

So in two seconds resumeTimer() method executes its code as stopTimer() does not impact it.

Question: Is there a way in JavaScript so when I execute stopTimer() it would cancel all pending code executions. So in this case I would like stopTimer() to cancel resumeTimer() code execution that would happen in 2 seconds.

Please say if this was unclear.

EXAMPLE:

<html>
<body>
 <div id="media-holder">
<span id="play-button" onclick="resumeTimer();"></span>
<span id="pause-button" onclick="stopTimer();"></span>
</div>


<script>
let timeVar = setTimeout(execute, 2000); 

function resumeTimer(intervalTime = 2000) {
    setTimeout(execute, intervalTime);
}

function stopTimer() {
    clearTimeout(timeVar);
}

function foo() {
    for(let i = 0; i < 100; i++) {
        console.log("execute");
    }
}

</script>
 <html>

IF you rapidly click the play-button and pause-button, it would still continue the execution of foo() method,


Solution

  • JavaScript is single-threaded. You cannot interrupt a function in the middle of execution, since it is blocking the thread from invoking the onclick callback until its execution is completed.

    However, you can design an interruptable function using a generator:

    function resumeTimer() {
      task.resume();
    }
    
    function stopTimer() {
      task.pause();
    }
    
    // the `*` means this is a generator function
    function* foo() {
      let i = 0;
    
      while (true) {
        // every `yield` statement is a point
        // where control-flow is interrupted
        yield console.log(i++);
      }
    }
    
    class Task {
      constructor(iterator, ms = 16, autoplay = true) {
        this.step = this.step.bind(this);
    
        this.iterator = iterator;
        this.ms = ms;
    
        if (autoplay) {
          this.resume();
        }
      }
    
      step() {
        this.result = this.iterator.next((this.result || {}).value);
      }
    
      pause() {
        if (this.ref) {
          clearInterval(this.ref);
          this.ref = null;
        }
      }
    
      resume(ms = this.ms) {
        if (ms !== this.ms) {
          this.pause();
          this.ms = ms;
        }
    
        if (!this.ref) {
          this.step();
          this.ref = setInterval(this.step, this.ms);
        }
      }
    }
    
    let task = new Task(foo());
    <button id="play-button" onclick="resumeTimer();">Play</button>
    <button id="pause-button" onclick="stopTimer();">Pause</button>

    References