Search code examples
javascriptsettimeoutrace-condition

Is there a risk that setTimeout will fire twice after I "reset" it?


Sometimes I use the following concept:

class ResetableTimeout extends EventEmitter {
  constructor() {
    this.id = -1;
  }
  start(delay) {
    clearTimeout(this.id);
    this.id = setTimeout(() =>{this.emit("done");}, delay);
  }
}

Architecture like this may be used for throttling operations for example.

Now I noticed that this may fire twice under these circumstances:

  1. setTimeout starts timeout
  2. start(delay) is called and is executing
  3. Timeout fires and the callback is pushed in the eventloop, waiting for start(delay) to end
  4. clearTimeout is called, but the timeout is done already
  5. start(delay) ends and timeout's callback executes
  6. delay ms later, timeout's callback executes again

Is this possible? If it is possible how to prevent it? If it isn't possible, what prevents it from happening?


Solution

  • No, this is not possible. clearTimeout will remove the timeout from the event queue if the internal timeout already had been fired. It assures that the callback will not run after you called clearTimeout.

    In the spec, there is a list of active timers, from which the callback to execute is fetched by the timed event loop task, and clearTimeout clears the timer in that list so even when the task had already been scheduled it will still check whether the timer is still active before executing the callback.