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

发布于 2024-09-07 08:52:22 字数 1549 浏览 7 评论 0原文

我正在编写一些与我不拥有的库代码交互的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 );

我想在屏幕上放置一个进度条,通过询问 setTimeout 创建的计时器,该进度条会向 questionTime * 1000 填充。唯一的问题是,似乎没有办法做到这一点。我是否缺少 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 );

这是我的代码:

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

这在 IE 6 以外的任何浏览器中都可以正常工作。即使是最初的 iPhone,我希望事情能够异步。

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

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


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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(14

欲拥i 2024-09-14 08:52:22

仅供记录,有一种方法可以获取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,我认为此评论可能对寻找的人有帮助为节点解决方案。

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.

女皇必胜 2024-09-14 08:52:22

编辑:我实际上认为我做了一个更好的: 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()

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()
童话里做英雄 2024-09-14 08:52:22

如果您无法修改库代码,则需要重新定义 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,以便在超时被清除时可以分离侦听器。

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.

睡美人的小仙女 2024-09-14 08:52:22

特定于服务器端 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+ 中使用刷新计时器处理对象可能会很有趣。

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+.

你是暖光i 2024-09-14 08:52:22

更快、更简单的方法:

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

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

 timeLeft = tmo - (performance.now() - start);

A quicker, easier way:

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

You can get the time remaining with:

 timeLeft = tmo - (performance.now() - start);
柠檬心 2024-09-14 08:52:22

Javascript 的事件堆栈并不像你想象的那样运作。

创建超时事件后,它会被添加到事件队列中,但在该事件被触发时,其他事件可能会优先,从而延迟执行时间并推迟运行时间。

示例:您创建一个延迟 10 秒的超时,以向屏幕发出提醒。它将被添加到事件堆栈中,并在所有当前事件被触发后执行(导致一些延迟)。然后,当超时处理完毕后,浏览器仍然继续捕获其他事件并将它们添加到堆栈中,这会导致处理进一步延迟。如果用户单击或进行大量 ctrl+打字,则他们的事件优先于当前堆栈。你的 10 秒可以变成 15 秒,甚至更长。


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

示例:执行 10 秒延迟的设置超时(将该延迟存储在全局中)。然后执行每秒运行一次的 setInterval 将延迟减去 1 并输出剩余的延迟。由于事件堆栈如何影响实际时间(如上所述),这仍然不准确,但确实给出了计数。


简而言之,没有真正的方法来获取剩余时间。只有尝试向用户传达估计值的方法。

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.

万劫不复 2024-09-14 08:52:22

我在这里停下来寻找这个答案,但过度思考了我的问题。如果您在这里是因为您只需要在 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);

I stopped by here looking for this answer, but was overthinking my problem. If you are here because you just need to keep track of time while you're setTimeout is in progress, here's another way to do it:

    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);
回忆凄美了谁 2024-09-14 08:52:22

您可以修改 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})();

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

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! :)

酒儿 2024-09-14 08:52:22

不,但是您可以在函数中为动画设置自己的 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 );
}

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 );
}
守望孤独 2024-09-14 08:52:22

问题已经得到解答,但我会补充一点。我刚刚想到的。

recursion中使用setTimeout如下:

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

我想代码是不言自明的

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");
    count++;

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

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

I guess the code is self explanatory

十年九夏 2024-09-14 08:52:22

检查此项:

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

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:

smth.get()

Clear timeout

smth.clear()
缘字诀 2024-09-14 08:52:22
    (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);
    (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);
驱逐舰岛风号 2024-09-14 08:52:22

对于任何需要钩子的人,请检查一下 - 应该是非常不言自明的。

请注意,elapsed 是一个内部状态变量,如果传递到钩子外部将是不正确的!

import { useEffect, useRef, useState } from 'react';

const useTimeout = (callback, duration, renderDuration = 5) => {
  const ref = useRef<any>(null);
  const [timeInfo, setTimeInfo] = useState<{
    start: number;
    elapsed: number;
    percentComplete: number;
  }>({
    start: null,
    elapsed: 0,
    percentComplete: 0
  });

  useEffect(() => {
    return () => {
      if (ref.current) {
        clearTimeout(ref.current);
        ref.current = null;
      }
    };
  }, []);

  useEffect(() => {
    setTimeout(() => {
      if (ref.current == null) return;
      setTimeInfo((prev) => {
        const elapsed = Date.now() - prev.start + prev.elapsed;

        if (ref.current == null) return prev;
        return {
          start: prev.start,
          elapsed: prev.elapsed,
          percentComplete: (elapsed / duration) * 100
        };
      });
    }, renderDuration);
  }, [timeInfo]);

  return {
    percentComplete: timeInfo.percentComplete,
    isTimerRunning: ref.current != null,
    startTimeout: () => {
      if (ref.current != null) return;
      setTimeInfo((prev) => ({ ...prev, start: Date.now() }));
      ref.current = setTimeout(callback, duration - timeInfo.elapsed);
    },
    stopTimeout: () => {
      if (ref.current) {
        clearTimeout(ref.current);
        ref.current = null;
      }
      setTimeInfo((prev) => {
        const elapsed = Date.now() - prev.start + prev.elapsed;
        return {
          start: prev.start,
          elapsed: elapsed,
          percentComplete: (elapsed / duration) * 100
        };
      });
    },
    resetTimeout: () => {
      if (ref.current) {
        ref.current = null;
        clearTimeout(ref.current);
      }
      setTimeInfo({ start: null, elapsed: 0, percentComplete: 0 });
    },
    restartTimeout: () => {
      if (ref.current) {
        ref.current = null;
        clearTimeout(ref.current);
      }
      setTimeInfo({ start: Date.now(), elapsed: 0, percentComplete: 0 });
      ref.current = setTimeout(callback, duration);
    }
  };
};

export default useTimeout;

For anyone in need of a hook, check this out - should be pretty self explanatory.

Note that elapsed is an internal state variable that if passed outside of the hook will be incorrect!

import { useEffect, useRef, useState } from 'react';

const useTimeout = (callback, duration, renderDuration = 5) => {
  const ref = useRef<any>(null);
  const [timeInfo, setTimeInfo] = useState<{
    start: number;
    elapsed: number;
    percentComplete: number;
  }>({
    start: null,
    elapsed: 0,
    percentComplete: 0
  });

  useEffect(() => {
    return () => {
      if (ref.current) {
        clearTimeout(ref.current);
        ref.current = null;
      }
    };
  }, []);

  useEffect(() => {
    setTimeout(() => {
      if (ref.current == null) return;
      setTimeInfo((prev) => {
        const elapsed = Date.now() - prev.start + prev.elapsed;

        if (ref.current == null) return prev;
        return {
          start: prev.start,
          elapsed: prev.elapsed,
          percentComplete: (elapsed / duration) * 100
        };
      });
    }, renderDuration);
  }, [timeInfo]);

  return {
    percentComplete: timeInfo.percentComplete,
    isTimerRunning: ref.current != null,
    startTimeout: () => {
      if (ref.current != null) return;
      setTimeInfo((prev) => ({ ...prev, start: Date.now() }));
      ref.current = setTimeout(callback, duration - timeInfo.elapsed);
    },
    stopTimeout: () => {
      if (ref.current) {
        clearTimeout(ref.current);
        ref.current = null;
      }
      setTimeInfo((prev) => {
        const elapsed = Date.now() - prev.start + prev.elapsed;
        return {
          start: prev.start,
          elapsed: elapsed,
          percentComplete: (elapsed / duration) * 100
        };
      });
    },
    resetTimeout: () => {
      if (ref.current) {
        ref.current = null;
        clearTimeout(ref.current);
      }
      setTimeInfo({ start: null, elapsed: 0, percentComplete: 0 });
    },
    restartTimeout: () => {
      if (ref.current) {
        ref.current = null;
        clearTimeout(ref.current);
      }
      setTimeInfo({ start: Date.now(), elapsed: 0, percentComplete: 0 });
      ref.current = setTimeout(callback, duration);
    }
  };
};

export default useTimeout;
伴我老 2024-09-14 08:52:22

我创建了一个类,允许您暂停和恢复超时和间隔,希望它有帮助!

class CustomTimer {
  constructor() {
    this._initState();
  }

  start(callback, delay, isInterval = false) {
    if (
      typeof callback !== "function" ||
      typeof delay !== "number" ||
      delay <= 0
    ) {
      throw new Error("Invalid arguments provided to start method.");
    }
    this.stop(); // Clear any previous timer

    this._userCallback = callback;
    this._startTime = Date.now();
    this._endTime = this._startTime + delay;
    this._remaining = delay;

    if (isInterval) {
      this._interval = delay;
    }

    this._startTimer(delay);
  }

  pause() {
    if (!this._timerId || this._isPaused) return;

    this._isPaused = true;
    this._remaining -= Date.now() - this._startTime;

    this._clearTimer();
  }

  resume() {
    if (!this._isPaused) return;

    this._startTimer(this._remaining);

    this._isPaused = false;
    this._startTime = Date.now();
    this._endTime = this._startTime + this._remaining;
  }

  stop() {
    this._clearTimer();
    this._initState();
  }

  _initState() {
    this._startTime = null;
    this._endTime = null;
    this._remaining = null;
    this._interval = null;
    this._userCallback = null;
    this._timerId = null;
    this._isPaused = false;
  }

  _startTimer(delay) {
    // If it's an interval and it's paused, then on resume, we just use a setTimeout.
    if (this._interval && this._isPaused) {
      this._timerId = setTimeout(() => {
        this._tick();
        this._timerId = setInterval(this._tick, this._interval);
      }, delay);
    } else {
      const timerFn = this._interval ? setInterval : setTimeout;
      this._timerId = timerFn(this._tick, delay);
    }
  }

  _clearTimer() {
    if (this._timerId) {
      // clearInterval works for both interval and timeout
      clearInterval(this._timerId);
      this._timerId = null;
    }
  }

  // Using an arrow function here ensures 'this' retains the current instance's context
  // We could also use .bind(this) in the constructor
  // We need to do this because setInterval and setTimeout change the context of 'this'
  _tick = () => {
    if (this._isPaused) return;

    this._userCallback();

    if (this._interval) {
      this._remaining = this._interval;
      this._startTime = Date.now();
      this._endTime = this._startTime + this._remaining;
    } else {
      this.stop();
    }
  };

  get timerId() {
    return this._timerId;
  }

  get timeRemaining() {
    if (this._isPaused) return this._remaining;
    return Math.max(this._endTime - Date.now(), 0);
  }
}

// Example usage

const timer = new CustomTimer();

timer.start(
  () => {
    console.log("Hello!");
  },
  5000,
  true
); // Execute the callback every 5 seconds

setTimeout(() => {
  timer.pause();
  console.log("Timer paused");
}, 2000); // Pause the timer after 2 seconds

setTimeout(() => {
  timer.resume(); // Resume the timer after 4 seconds (2 seconds after it was paused)
}, 4000);

setInterval(() => {
  console.log(`Time remaining: ${timer.timeRemaining}ms.`); // Get the time remaining every second
}, 1000);

I created a class to allow you to pause and resume timeouts and intervals, hope it helps!

class CustomTimer {
  constructor() {
    this._initState();
  }

  start(callback, delay, isInterval = false) {
    if (
      typeof callback !== "function" ||
      typeof delay !== "number" ||
      delay <= 0
    ) {
      throw new Error("Invalid arguments provided to start method.");
    }
    this.stop(); // Clear any previous timer

    this._userCallback = callback;
    this._startTime = Date.now();
    this._endTime = this._startTime + delay;
    this._remaining = delay;

    if (isInterval) {
      this._interval = delay;
    }

    this._startTimer(delay);
  }

  pause() {
    if (!this._timerId || this._isPaused) return;

    this._isPaused = true;
    this._remaining -= Date.now() - this._startTime;

    this._clearTimer();
  }

  resume() {
    if (!this._isPaused) return;

    this._startTimer(this._remaining);

    this._isPaused = false;
    this._startTime = Date.now();
    this._endTime = this._startTime + this._remaining;
  }

  stop() {
    this._clearTimer();
    this._initState();
  }

  _initState() {
    this._startTime = null;
    this._endTime = null;
    this._remaining = null;
    this._interval = null;
    this._userCallback = null;
    this._timerId = null;
    this._isPaused = false;
  }

  _startTimer(delay) {
    // If it's an interval and it's paused, then on resume, we just use a setTimeout.
    if (this._interval && this._isPaused) {
      this._timerId = setTimeout(() => {
        this._tick();
        this._timerId = setInterval(this._tick, this._interval);
      }, delay);
    } else {
      const timerFn = this._interval ? setInterval : setTimeout;
      this._timerId = timerFn(this._tick, delay);
    }
  }

  _clearTimer() {
    if (this._timerId) {
      // clearInterval works for both interval and timeout
      clearInterval(this._timerId);
      this._timerId = null;
    }
  }

  // Using an arrow function here ensures 'this' retains the current instance's context
  // We could also use .bind(this) in the constructor
  // We need to do this because setInterval and setTimeout change the context of 'this'
  _tick = () => {
    if (this._isPaused) return;

    this._userCallback();

    if (this._interval) {
      this._remaining = this._interval;
      this._startTime = Date.now();
      this._endTime = this._startTime + this._remaining;
    } else {
      this.stop();
    }
  };

  get timerId() {
    return this._timerId;
  }

  get timeRemaining() {
    if (this._isPaused) return this._remaining;
    return Math.max(this._endTime - Date.now(), 0);
  }
}

// Example usage

const timer = new CustomTimer();

timer.start(
  () => {
    console.log("Hello!");
  },
  5000,
  true
); // Execute the callback every 5 seconds

setTimeout(() => {
  timer.pause();
  console.log("Timer paused");
}, 2000); // Pause the timer after 2 seconds

setTimeout(() => {
  timer.resume(); // Resume the timer after 4 seconds (2 seconds after it was paused)
}, 4000);

setInterval(() => {
  console.log(`Time remaining: ${timer.timeRemaining}ms.`); // Get the time remaining every second
}, 1000);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文