Search code examples
javascriptreactjsservice-workercountdownweb-worker

clearInterval in web worker not stopping timer


my question has been asked once here: clearInterval in webworker is not working

The solution seems clear but for some reason it is not working for me yet. I have a web worker that is sending an interval back to the main thread. I want to be able to stop the interval with clearInterval, but it is not working.

I have it set up exactly the same as it suggests in the previous question, but still no luck. I have added some console.logs to verify I'm in the correct block. "Stop" logs to the console when it supposed to, but the timer doesn't stop posting to the main thread.

Can anyone spot what's going on here?

Thanks

worker.js

let mytimer;

self.onmessage = function(evt) {
    if (evt.data == "start") {
        console.log("start")
        var i = 0;
        
        mytimer = setInterval(function() {
            i++;
            postMessage(i);
        }, 1000);
        
    } else if (evt.data == "stop") {
        console.log("stop")

        clearInterval(mytimer);
    }
};

Then I'm calling this from my React hook when timer.time is above or below a certain value (2000 in this case)

main.js


  const worker = new myWorker()

 useEffect(() => {

    worker.addEventListener('message', function(e) {

      //from interval in the worker
      console.log('Message from Worker: ' + e.data);
    })

    if(timer.time > 2000){
      worker.postMessage("start")
    }else{

      worker.postMessage("stop")
    }
  },[timer.time])


Solution

  • You should also clear the interval when you start a new interval. If you don't do it, your previous interval would keep running, and you'll lose the ability to clear it:

    let mytimer;
    
    self.onmessage = function(evt) {
      console.log(evt.data)
      
      if(evt.data === 'start' || evt.data === 'stop') {
        clearInterval(mytimer);
      }
    
      if (evt.data == "start") {
        var i = 0;
    
        mytimer = setInterval(function() {
          i++;
          postMessage(i);
        }, 1000);
    
      }
    };
    

    You should create a single instance of the worker, and store it as ref:

    const worker = useRef()
    
    useEffect(() => {
      worker.current = new myWorker()
    
      return () => {
        worker.current.terminate();
      }
    }, [])
    

    Not related, but in addition, the useEffect adds a new event listener whenever timer.time changes, without clearing the previous one. I would split this into 2 useEffect blocks, one for sending (which can be combind with the creation of the worker), and the other for receiving.

    useEffect(() => {
      const eventHander = e => {
        //from interval in the worker
        console.log('Message from Worker: ' + e.data);
      }
    
      worker.current.addEventListener('message', eventHander)
      
      return () => {
        worker.current.removeEventListener('message', eventHander)
      }
    }, [])
    
    useEffect(() => {
      worker.current.postMessage(timer.time > 2000 ? 'start' : 'stop')
    }, [timer.time])