在setTimeout()中如何查找剩余时间?

105
我正在编写一些与我无法拥有并且无法(合理地)更改的库代码交互的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);
}

简述:如何找到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的包装函数
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上也是如此。

15个回答

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


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

如果有需要使用钩子的人,请查看这个 - 应该很容易理解。

请注意,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;

你的回答可以通过提供更多支持性信息来改进。请[编辑]以添加更多细节,以便更容易理解你的回答。你可以在帮助中心找到有关如何编写好答案的更多信息。 - SK-the-Learner

0
我创建了一个类,可以让你暂停和恢复超时和间隔,希望能帮到你!
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);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接