在setTimeout()中找到剩余时间?


90

我正在编写一些Javascript,该Javascript与我不拥有的并且不能(合理地)更改的库代码交互。它创建Javascript超时,用于显示一系列限时问题中的下一个问题。这不是真正的代码,因为它被所有的希望所迷惑。库正在执行以下操作:

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

我想在屏幕上放置一个进度条,questionTime * 1000通过查询由创建的计时器来填充进度条setTimeout。唯一的问题是,似乎没有办法做到这一点。getTimeout我有缺少的功能吗?我可以找到的有关Javascript超时的唯一信息仅与通过创建setTimeout( function, time)和通过删除有关clearTimeout( id )

我正在寻找一个函数,该函数返回超时触发前的剩余时间或调用超时后经过的时间。我的进度条代码如下所示:

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:如何找到javascript setTimeout()之前的剩余时间?


这是我现在正在使用的解决方案。我遍历了负责测试的库部分,并且对代码进行了解读(这很糟糕,并且未经我的许可)。

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

这是我的代码:

// setTimeout的包装器
函数mySetTimeout(func,timeout){
    超时[n = setTimeout(func,timeout)] = {
        开始:new Date()。getTime(),
        结束:新的Date()。getTime()+超时
        t:超时
    }
    返回n;
}

在不是IE 6的任何浏览器中,这种方法都可以正常工作。

Answers:


31

如果您无法修改库代码,则需要重新定义setTimeout以适合您的目的。这是您可以做什么的示例:

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

这不是生产代码,但是它应该使您走上正确的道路。请注意,每个超时您只能绑定一个侦听器。我没有对此进行广泛的测试,但是它可以在Firebug中使用。

更加健壮的解决方案将使用与包装setTimeout相同的技术,但是使用从返回的timeoutId到侦听器的映射来处理每个超时的多个侦听器。您还可以考虑包装clearTimeout,以便在清除超时后分离监听器。


谢谢,您保存了我的一天!
xecutioner 2014年

14
由于执行时间,这可能会在一段时间内漂移几毫秒。一种更安全的方法是记录您开始超时的时间(新的Date()),然后从当前时间中减去(另一个新的Date())
Jonathan

61

仅作记录,有一种方法可以在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

这似乎无法在Firefox中正常工作,但是由于node.js是javascript,因此我认为此评论可能对寻找节点解决方案的人有所帮助。


5
不知道这是节点的新版本还是什么版本,但是为了使其正常工作,我不得不删除了“ getTime()”部分。似乎_idleStart现在已经是整数了
kasztelan

3
我刚刚在节点0.10中尝试过,getTime()已删除,_idleStart已经是时候了
Tan Nguyen

2
在节点6.3上不起作用,因为超时结构是这些信息的泄漏。
2016年

53

编辑:我实际上认为我做得更好:https : //stackoverflow.com/a/36389263/2378102

我编写了此函数,并使用了很多:

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()
}

设置一个计时器:

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

因此,如果您想剩余时间,请执行以下操作:

a.getTimeLeft()

感谢那。并不是我一直在寻找的东西,但是计算剩余时间的功能非常有用
而是

4

Javascript的事件堆栈不会按照您的想法进行操作。

创建超时事件后,会将其添加到事件队列,但是在触发该事件时,其他事件可能会具有优先级,从而延迟执行时间并延迟运行时间。

示例: 您创建一个超时,延迟时间为10秒,以向屏幕发出警报。它会被添加到事件堆栈中,并会在所有当前事件触发后执行(造成一些延迟)。然后,在处理超时后,浏览器仍会继续捕获其他事件,将它们添加到堆栈中,这将导致处理的进一步延迟。如果用户单击或进行许多Ctrl +型输入,则他们的事件将优先于当前堆栈。您的10秒可能变成15秒或更长时间。


话虽这么说,有很多方法可以伪造多少时间。一种方法是在将setTimeout添加到堆栈后立即执行setInterval。

示例:以10秒的延迟执行settimeout(将该延迟存储在全局变量中)。然后执行一个setInterval,它每秒运行一次以从延迟中减去1并输出剩余的延迟。由于事件堆栈如何影响实际时间(如上所述),因此仍然不准确,但是可以计数。


简而言之,没有真正的方法来获取剩余时间。只有几种方法可以尝试将估算结果传达给用户。


4

服务器端特定于Node.js

以上都不是我真正的工作,在检查了超时对象之后,一切似乎都与该过程开始的时间有关。以下为我工作:

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

输出:

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

node -v
v9.11.1

为简洁起见,编辑了输出,但是此基本功能给出了从执行到执行的大致时间。就像其他人提到的那样,由于节点处理的方式,所有这些都不是准确的,但是如果我想抑制运行时间少于1分钟的请求,并且我存储了计时器,我不明白为什么这样做不会快速检查工作。在10.2及更高版本中使用refreshtimer处理对象可能很有趣。


1

您可以修改 setTimeout以将每个超时的结束时间存储在地图中,并创建一个名为的函数getTimeout来获取具有特定ID的超时的剩余时间。

这是super解决方案,但我对其进行了修改,以使用较少的内存

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

用法:

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

这是此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})();

我希望这对您和我一样有用!:)


0

不,但是您可以在函数中设置自己的动画setTimeout / setInterval。

说您的问题如下所示:

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

  // do the question stuff
  // ...
}

您的动画将通过以下两个功能处理:

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

因为动画是从函数顶部开始的,所以差异只能以毫秒为单位。我看到您使用jQuery。然后可以使用jquery.animate。
gblazex

最好的方法是亲自尝试一下。:)
gblazex

0

如果有人回头看这个。我提供了一个超时和间隔管理器,它可以为您提供超时或间隔中剩余的时间,以及进行其他操作。我将添加它以使其更精美,更准确,但是它似乎仍然可以正常工作(尽管我还有更多的想法可以使其更准确):

https://github.com/vhmth/Tock


您的链接已损坏
Kick Buttowski

0

问题已经回答,但我会补充一点。它只是发生在我身上。

使用setTimeoutrecursion,如下所示:

var count = -1;

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

    if(count <20)
    {
        console.log(20-count+"seconds left");
        setTimeout(beginTimer,2000);
    }
    else
    {
        endTimer();
    }
}

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

我猜代码是不言自明的


0

检查这一项:

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

设置一个计时器:

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

保持:

smth.get()

清除超时

smth.clear()

0
    (function(){
        window.activeCountdowns = [];
        window.setCountdown = function (code, delay, callback, interval) {
            var timeout = delay;
            var timeoutId = setTimeout(function(){
                clearCountdown(timeoutId);
                return code();
            }, delay);
            window.activeCountdowns.push(timeoutId);
            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) {
            clearTimeout(timeoutId);
            var key = window.activeCountdowns.indexOf(timeoutId);
            if (key < 0) return;
            window.activeCountdowns.splice(key, 1);
        };
    })();

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

0

我在这里停下来寻找答案,但我想得太多了。如果您在这里是因为只需要在setTimeout进行时跟踪时间,这是另一种方法:

    var focusTime = parseInt(msg.time) * 1000

    setTimeout(function() {
        alert('Nice Job Heres 5 Schrute bucks')
        clearInterval(timerInterval)
    }, focusTime)

    var timerInterval = setInterval(function(){
        focusTime -= 1000
        initTimer(focusTime / 1000)
    }, 1000);

0

更快,更简单的方法:

tmo = 1000;
start = performance.now();
setTimeout(function(){
    foo();
},tmo);

您可以通过以下方式获得剩余时间:

 timeLeft = tmo - (performance.now() - start);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.