Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

find the time left in a setTimeout()?

People also ask

What is setTimeout ()?

The setTimeout function is a native JavaScript function. It sets a timer (a countdown set in milliseconds) for an execution of a callback function, calling the function upon completion of the timer.

Why is setTimeout executed at last?

setTimeout schedules the function for execution. That scheduler doesn't do its thing until after the current thread yields control back to the browser, e.g. after the last logging statement has executed.

Does setTimeout return value?

The setTimeout() returns a timeoutID which is a positive integer identifying the timer created as a result of calling the method. The timeoutID can be used to cancel timeout by passing it to the clearTimeout() method.


Just for the record, there is a way to get the time left in node.js:

var timeout = setTimeout(function() {}, 3600 * 1000);

setInterval(function() {
    console.log('Time left: '+getTimeLeft(timeout)+'s');
}, 2000);

function getTimeLeft(timeout) {
    return Math.ceil((timeout._idleStart + timeout._idleTimeout - Date.now()) / 1000);
}

Prints:

$ node test.js 
Time left: 3599s
Time left: 3597s
Time left: 3595s
Time left: 3593s

This doesn't seem to work in firefox through, but since node.js is javascript, I thought this remark might be helpful for people looking for the node solution.


EDIT: I actually think I made an even better one: https://stackoverflow.com/a/36389263/2378102

I wrote this function and I use it a lot:

function timer(callback, delay) {
    var id, started, remaining = delay, running

    this.start = function() {
        running = true
        started = new Date()
        id = setTimeout(callback, remaining)
    }

    this.pause = function() {
        running = false
        clearTimeout(id)
        remaining -= new Date() - started
    }

    this.getTimeLeft = function() {
        if (running) {
            this.pause()
            this.start()
        }

        return remaining
    }

    this.getStateRunning = function() {
        return running
    }

    this.start()
}

Make a timer:

a = new timer(function() {
    // What ever
}, 3000)

So if you want the time remaining just do:

a.getTimeLeft()

If you can't modify the library code, you'll need to redefine setTimeout to suit your purposes. Here's an example of what you could do:

(function () {
var nativeSetTimeout = window.setTimeout;

window.bindTimeout = function (listener, interval) {
    function setTimeout(code, delay) {
        var elapsed = 0,
            h;

        h = window.setInterval(function () {
                elapsed += interval;
                if (elapsed < delay) {
                    listener(delay - elapsed);
                } else {
                    window.clearInterval(h);
                }
            }, interval);
        return nativeSetTimeout(code, delay);
    }

    window.setTimeout = setTimeout;
    setTimeout._native = nativeSetTimeout;
};
}());
window.bindTimeout(function (t) {console.log(t + "ms remaining");}, 100);
window.setTimeout(function () {console.log("All done.");}, 1000);

This is not production code, but it should put you on the right track. Note that you can only bind one listener per timeout. I haven't done extensive testing with this, but it works in Firebug.

A more robust solution would use the same technique of wrapping setTimeout, but instead use a map from the returned timeoutId to listeners to handle multiple listeners per timeout. You might also consider wrapping clearTimeout so you can detach your listener if the timeout is cleared.


Server side Node.js specific

None of the above really worked for me, and after inspecting the timeout object it looked like everything was relative to when the process started. The following worked for me:

myTimer = setTimeout(function a(){console.log('Timer executed')},15000);

function getTimeLeft(timeout){
  console.log(Math.ceil((timeout._idleStart + timeout._idleTimeout)/1000 - process.uptime()));
}

setInterval(getTimeLeft,1000,myTimer);

Output:

14
...
3
2
1
Timer executed
-0
-1
...

node -v
v9.11.1

Edited output for brevity, but this basic function gives a approximate time until execution or since execution. As others mention, none of this will be exact due to the way node processes, but if I want to suppress a request that was run less than 1 minute ago, and I stored the timer, I don't see why this wouldn't work as a quick check. Could be interesting to juggle objects with refreshtimer in 10.2+.


Javascript's event stacks don't operate how you would think.

When a timeout event is created, it is added to the event queue, but other events may take priority while that event is being fired, delay the execution time and postponing runtime.

Example: You create a timeout with a delay of 10 seconds to alert something to the screen. It will be added to the event stack and will be executed after all current events are fired (causing some delay). Then, when the timeout is processed, the browser still continues to capture other events add them to the stack, which causes further delays in the processing. If the user clicks, or does a lot of ctrl+typing, their events take priority over the current stack. Your 10 seconds can turn into 15 seconds, or longer.


That being said, there are many ways to fake how much time has passed. One way is to execute a setInterval right after you add the setTimeout to the stack.

Example: Perform a settimeout with a 10 second delay (store that delay in a global). Then perform a setInterval that runs every second to subtract 1 from the delay and output the delay remaining. Because of how the event stack can influence actual time (described above), this still won't be accurate, but does give a count.


In short, there is no real way to get the remaining time. There are only ways to try and convey an estimate to the user.


You can modify setTimeout to store each timeout's end time in a map and create a function called getTimeout to get the time left for a timeout with a certain id.

This was super's solution, but I modified it to use slightly less memory

let getTimeout = (() => { // IIFE
    let _setTimeout = setTimeout, // Reference to the original setTimeout
        map = {}; // Map of all timeouts with their end times

    setTimeout = (callback, delay) => { // Modify setTimeout
        let id = _setTimeout(callback, delay); // Run the original, and store the id
        map[id] = Date.now() + delay; // Store the end time
        return id; // Return the id
    };

    return (id) => { // The actual getTimeout function
        // If there was no timeout with that id, return NaN, otherwise, return the time left clamped to 0
        return map[id] ? Math.max(map[id] - Date.now(), 0) : NaN;
    }
})();

Usage:

// go home in 4 seconds
let redirectTimeout = setTimeout(() => {
    window.location.href = "/index.html";
}, 4000);

// display the time left until the redirect
setInterval(() => {
    document.querySelector("#countdown").innerHTML = `Time left until redirect ${getTimeout(redirectTimeout)}`;
},1);

Here's a minified version of this getTimeout IIFE:

let getTimeout=(()=>{let t=setTimeout,e={};return setTimeout=((a,o)=>{let u=t(a,o);return e[u]=Date.now()+o,u}),t=>e[t]?Math.max(e[t]-Date.now(),0):NaN})();

I hope this is as useful to you as it was for me! :)