skip to Main Content

I have a function that mutates some global state. How can I abort this function when a certain condition comes true in order to prevent a race condition. Here is a code sample to clarify.

let globalState = 0
const mutateState = () => {
    setTimeout(()=> {
        globalState = Math.random()
    }, Math.random() * 1000);
}
<button onclick="mutateState()"></button>

In essence when the user clicks that button I need to cancel the previous call to mutateState function so that it doesn’t alter the global state.

Also for context I am using Vuejs (not in this example though).

Putting an if statement before the globalState assignment is not ideal because in my real code I have a lot of variables and a lot of places in the code that they are mutated, it becomes tedious to put an if statement in front of each of them.

Ideally I want a way to abort the function completely.

2

Answers


  1. You can simply call clearTimeout(yourTimeoutId) to cancel its callback:

    let globalState = 0
    let timeoutId = null;
    const mutateState = () => {
        timeoutId = setTimeout(()=> {
            globalState = Math.random()
        }, Math.random() * 1000);
    }
    
    const cancelMutate = () => {
      clearTimeout(timeout);
    }
    
    Login or Signup to reply.
  2. There is no general answer because this mainly depends on the context.

    However, there is AbortController, which can be used as a standardized way to abort a control flow. It doesn’t do any magic that would automatically abort something, but APIs can use it to check if a process has to be aborted.

    fetch is an example where it can be used:

      controller = new AbortController();
      const signal = controller.signal;
      fetch(url, { signal })
    

    And with controller.abort(); that fetch can be aborted.

    You can use that controller also in your code:

    let globalState = 0
    const mutateState = (() => {
      let abortController;
    
      return function() {
        abortController?.abort();
    
        abortController = new AbortController();
    
        setAbortableTimeout(() => {
            globalState = Math.random();
            
            console.log(abortController.signal.aborted);
            console.log(globalState);
            
          },
          Math.random() * 1000,
          abortController.signal);
      }
    })()
    
    function setAbortableTimeout(cb, time, signal) {
      let timer = setTimeout(cb, time);
    
      signal.addEventListener("abort", () => {
        clearTimeout(timer);
      });
    
      return timer
    }
    
    document.querySelector('button').addEventListener('click', mutateState);
    <button>click me</button>

    Either by checking if the state of signal.aborted, or using signal.addEventListener("abort", …) to the abort event.

    The above code is a simplified example, but I hope you understand how to use it.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search