[英]find the time left in a setTimeout()?

I'm writing some Javascript that interacts with library code that I don't own, and can't (reasonably) change. It creates Javascript timeouts used for showing the next question in a series of time-limited questions. This isn't real code because it is obfuscated beyond all hope. Here's what the library is doing:


// setup a timeout to go to the next question based on user-supplied time
var t = questionTime * 1000
test.currentTimeout = setTimeout( showNextQuestion(questions[i+1]), t );

I want to put a progress bar onscreen that fills towards questionTime * 1000 by interrogating the timer created by setTimeout. The only problem is, there seems to be no way to do this. Is there a getTimeout function that I'm missing? The only information on Javascript timeouts that I can find is related only to creation via setTimeout( function, time) and deletion via clearTimeout( id ).

我想在屏幕上放置一個進度條,通過詢問setTimeout創建的計時器來填充questionTime * 1000。唯一的問題是,似乎沒有辦法做到這一點。有一個getTimeout函數我漏掉了嗎?我能找到的關於Javascript超時的唯一信息只與通過setTimeout(函數,時間)創建和通過clearTimeout(id)刪除有關。

I'm looking for a function that returns either the time remaining before a timeout fires, or the time elapsed after a timeout has been called. My progress bar code looks like this:


var  timeleft = getTimeout( test.currentTimeout ); // I don't know how to do this
var  $bar = $('.control .bar');
while ( timeleft > 1 ) {
    $bar.width(timeleft / test.defaultQuestionTime * 1000);

tl;dr: How do I find the time remaining before a javascript setTimeout()?

tl;dr:如何找到javascript setTimeout()之前的剩余時間?

Here's the solution I'm using now. I went through the library section that's in charge of tests, and unscrambled the code (terrible, and against my permissions).


// setup a timeout to go to the next question based on user-supplied time
var t = questionTime * 1000
test.currentTimeout = mySetTimeout( showNextQuestion(questions[i+1]), t );

and here's my code:


// wrapper for setTimeout
function mySetTimeout( func, timeout ) {
    timeouts[ n = setTimeout( func, timeout ) ] = {
        start: new Date().getTime(),
        end: new Date().getTime() + timeout
        t: timeout
    return n;

This works pretty spot-on in any browser that isn't IE 6. Even the original iPhone, where I expected things to get asynchronous.

這在IE 6之外的任何瀏覽器中都很有效。甚至是最初的iPhone,我希望它能實現異步。

10 个解决方案



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 = window.setInterval(function () {
                elapsed += interval;
                if (elapsed < delay) {
                    listener(delay - elapsed);
                } else {
            }, 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.




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);



$ 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
        remaining -= new Date() - started

    this.getTimeLeft = function() {
        if (running) {

        return remaining

    this.getStateRunning = function() {
        return running


Make a timer:


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

So if you want the time remaining just do:





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.




Here is maybe an even better way to do it, plus, you won't need to change code you've already written:


var getTimeout = (function() { // IIFE
    var _setTimeout = setTimeout, // Reference to the original setTimeout
        map = {}; // Map of all timeouts with their start date and delay

    setTimeout = function(callback, delay) { // Modify setTimeout
        var id = _setTimeout(callback, delay); // Run the original, and store the id

        map[id] = [Date.now(), delay]; // Store the start date and delay

        return id; // Return the id

    return function(id) { // The actual getTimeLeft function
        var m = map[id]; // Find the timeout in map

        // If there was no timeout with that id, return NaN, otherwise, return the time left clamped to 0
        return m ? Math.max(m[1] - Date.now() + m[0], 0) : NaN;

... And mimimized:


var getTimeout=function(){var e=setTimeout,b={};setTimeout=function(a,c){var d=e(a,c);b[d]=[Date.now(),c];return d};return function(a){return(a=b[a])?Math.max(a[1]-Date.now()+a[0],0):NaN}}();



No, but you can have your own setTimeout/setInterval for animation in your function.


Say your question looks like this:


function myQuestion() {
  // animate the progress bar for 1 sec
  animate( "progressbar", 1000 );

  // do the question stuff
  // ...

And your animation will be handled by these 2 functions:


function interpolate( start, end, pos ) {
  return start + ( pos * (end - start) );

function animate( dom, interval, delay ) {

      interval = interval || 1000;
      delay    = delay    || 10;

  var start    = Number(new Date());

  if ( typeof dom === "string" ) {
    dom = document.getElementById( dom );

  function step() {

    var now     = Number(new Date()),
        elapsed = now - start,
        pos     = elapsed / interval,
        value   = ~~interpolate( 0, 500, pos ); // 0-500px (progress bar)

    dom.style.width = value + "px";

    if ( elapsed < interval )
      setTimeout( step, delay );

  setTimeout( step, delay );



If anyone's looking back on this. I've come out with a timeout and interval manager that can get you the time left in a timeout or interval as well as do some other stuff. I'll be adding to it to make it more nifty and more accurate, but it seems to work fairly well as is (although I have some more ideas to make it even more accurate):






Question has already been answered but I will add my bit. It just occured to me.


Use setTimeout in recursion as follows:


var count = -1;

function beginTimer()
    console.log("Counting 20 seconds");

    if(count <20)
        console.log(20-count+"seconds left");

function endTimer()
    console.log("Time is finished");

I guess the code is self explanatory




Check this one:


class Timer {
  constructor(fun,delay) {
    this.timer=setTimeout(fun, delay)
    this.stamp=new Date()
  get(){return ((this.timer._idleTimeout - (new Date-this.stamp))/1000) }
  clear(){return (this.stamp=null, clearTimeout(this.timer))}

Make a timer:


let smtg = new Timer(()=>{do()}, 3000})

Get remain:



Clear timeout





        window.activeCountdowns = [];
        window.setCountdown = function (code, delay, callback, interval) {
            var timeout = delay;
            var timeoutId = setTimeout(function(){
                return code();
            }, delay);
            setTimeout(function countdown(){
                var key = window.activeCountdowns.indexOf(timeoutId);
                if (key < 0) return;
                timeout -= interval;
                setTimeout(countdown, interval);
                return callback(timeout);
            }, interval);
            return timeoutId;
        window.clearCountdown = function (timeoutId) {
            var key = window.activeCountdowns.indexOf(timeoutId);
            if (key < 0) return;
            window.activeCountdowns.splice(key, 1);

    var t = setCountdown(function () {
    }, 15000, function (i) {
        console.log(i / 1000);
    }, 1000);



粤ICP备14056181号  © 2014-2020 ITdaan.com