javascript: pause setTimeout();

Viewed 134730

If I have an active timeout running that was set through var t = setTimeout("dosomething()", 5000),

Is there anyway to pause and resume it?


Is there any way to get the time remaining on the current timeout?
or do I have to in a variable, when the timeout is set, store the current time, then we we pause, get the difference between now and then?

20 Answers

Typescript implementation based on top rated answer

/** Represents the `setTimeout` with an ability to perform pause/resume actions */
export class Timer {
    private _start: Date;
    private _remaining: number;
    private _durationTimeoutId?: NodeJS.Timeout;
    private _callback: (...args: any[]) => void;
    private _done = false;
    get done () {
        return this._done;
    }

    constructor(callback: (...args: any[]) => void, ms = 0) {
        this._callback = () => {
            callback();
            this._done = true;
        };
        this._remaining = ms;
        this.resume();
    }

    /** pauses the timer */
    pause(): Timer {
        if (this._durationTimeoutId && !this._done) {
            this._clearTimeoutRef();
            this._remaining -= new Date().getTime() - this._start.getTime();
        }
        return this;
    }

    /** resumes the timer */
    resume(): Timer {
        if (!this._durationTimeoutId && !this._done) {
            this._start = new Date;
            this._durationTimeoutId = setTimeout(this._callback, this._remaining);
        }
        return this;
    }

    /** 
     * clears the timeout and marks it as done. 
     * 
     * After called, the timeout will not resume
     */
    clearTimeout() {
        this._clearTimeoutRef();
        this._done = true;
    }

    private _clearTimeoutRef() {
        if (this._durationTimeoutId) {
            clearTimeout(this._durationTimeoutId);
            this._durationTimeoutId = undefined;
        }
    }

}

You could also implement it with events.

Instead of calculating the time difference, you start and stop listening to a 'tick' event which keeps running in the background:

var Slideshow = {

  _create: function(){                  
    this.timer = window.setInterval(function(){
      $(window).trigger('timer:tick'); }, 8000);
  },

  play: function(){            
    $(window).bind('timer:tick', function(){
      // stuff
    });       
  },

  pause: function(){        
    $(window).unbind('timer:tick');
  }

};
function delay (ms)   {  return new Promise(resolve => setTimeout(resolve, s));  }

"async" working demo at: site zarsoft.info

You can do like below to make setTimeout pausable on server side (Node.js)

const PauseableTimeout = function(callback, delay) {
    var timerId, start, remaining = delay;

    this.pause = function() {
        global.clearTimeout(timerId);
        remaining -= Date.now() - start;
    };

    this.resume = function() {
        start = Date.now();
        global.clearTimeout(timerId);
        timerId = global.setTimeout(callback, remaining);
    };

    this.resume();
};

and you can check it as below

var timer = new PauseableTimeout(function() {
    console.log("Done!");
}, 3000);
setTimeout(()=>{
    timer.pause();
    console.log("setTimeout paused");
},1000);

setTimeout(()=>{
    console.log("setTimeout time complete");
},3000)

setTimeout(()=>{
    timer.resume();
    console.log("setTimeout resume again");
},5000)
class pausable_timeout {
  constructor(func, milliseconds) {
    this.func = func;
    this.date_ms = new Date().valueOf();
    this.timeout = setTimeout(func, milliseconds);
    this.time_left = milliseconds;
  };

  pause() {
    clearTimeout(this.timeout);
    const elapsed_time = new Date().valueOf() - this.date_ms;
    this.time_left -= elapsed_time;
  };

  unpause() {
    this.timeout = setTimeout(this.func, this.time_left);
    this.date_ms = new Date().valueOf();
  };
};

const timer = new pausable_timeout(() => /* your code */, 3000 /* your timeout in milliseconds */);
timer.pause();
timer.unpause();

The programme is rather simple. We will create a class containing two functions, the pause function and the unpause function.

The pause function will clear the setTimeout and store the time that has elapsed between the start and now in the time_left variable. The unpause function will recreate a setTimeout by putting the time_left time as an argument.

If anyone wants the TypeScript version shared by the Honorable @SeanVieira here, you can use this:

    public timer(fn: (...args: any[]) => void, countdown: number): { onCancel: () => void, onPause: () => void, onResume: () => void } {
        let ident: NodeJS.Timeout | number;
        let complete = false;
        let totalTimeRun: number;
        const onTimeDiff = (date1: number, date2: number) => {
            return date2 ? date2 - date1 : new Date().getTime() - date1;
        };

        const handlers = {
            onCancel: () => {
                clearTimeout(ident as NodeJS.Timeout);
            },
            onPause: () => {
                clearTimeout(ident as NodeJS.Timeout);
                totalTimeRun = onTimeDiff(startTime, null);
                complete = totalTimeRun >= countdown;
            },
            onResume: () => {
                ident = complete ? -1 : setTimeout(fn, countdown - totalTimeRun);
            }
        };

        const startTime = new Date().getTime();
        ident = setTimeout(fn, countdown);

        return handlers;
    }

I created this code in TypeScript for slider feature:

class TimeoutSlider {
  private callback: () => void;
  private duration: number;
  private timeReaming: number;
  private startTime: number | null = null;
  private timerId: NodeJS.Timeout | null = null;

  constructor(callback: () => void, duration: number) {
    this.callback = callback;
    this.duration = duration;
    this.timeReaming = duration;
  }

  public start() {
    this.clear();
    this.startTime = new Date().getTime();
    this.timerId = setTimeout(this.callback, this.duration);
  }

  public pause() {
    if (!this.startTime) {
      throw new Error("Cannot pause a timer that has not been started");
    }
    this.clear();
    this.timeReaming = this.duration - (new Date().getTime() - this.startTime);
  }

  public resume() {
    this.clear();
    this.startTime = new Date().getTime();
    this.timerId = setTimeout(this.callback, this.timeReaming);
  }

  private clear() {
    if (this.timerId) {
      clearTimeout(this.timerId);
      this.timerId = null;
    }
  }
}
Related