найти оставшееся время в setTimeout ()?

Я пишу некоторый 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 );

а вот мой код:

// 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, где я ожидал, что все будет асинхронным.


person Just Jake    schedule 29.06.2010    source источник


Ответы (12)


Если вы не можете изменить код библиотеки, вам нужно переопределить 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, чтобы вы могли отсоединить слушателя, если тайм-аут очищен.

person lawnsea    schedule 30.06.2010
comment
Спасибо, вы спасли мне день! - person xecutioner; 15.01.2014
comment
Из-за времени выполнения это может измениться на несколько миллисекунд в течение некоторого времени. Более безопасный способ сделать это - записать время, когда вы начали тайм-аут (new Date ()), а затем просто вычесть его из текущего времени (еще один новый Date ()) - person Jonathan; 22.04.2015

Для записи, есть способ узнать оставшееся время в 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, я подумал, что это замечание может быть полезно для людей, которые ищут решение node.

person Fluffy    schedule 28.05.2012
comment
не уверен, что это новая версия узла или что-то еще, но для того, чтобы это работало, мне пришлось удалить часть getTime (). кажется, что _idleStart уже является целым числом - person kasztelan; 17.11.2013
comment
Я только что пробовал в узле 0.10, getTime() удален, _idleStart уже пора - person Tan Nguyen; 11.02.2014
comment
не работает на узле 6.3, потому что структура тайм-аута является утечкой этой информации. - person Huan; 15.07.2016

РЕДАКТИРОВАТЬ: я действительно думаю, что сделал еще лучше: 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()
person Community    schedule 23.12.2013
comment
Спасибо за это. Не совсем то, что я искал, но функция подсчета оставшегося времени очень полезна - person instead; 09.04.2014

Специфично для 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+.

person John Watts    schedule 15.07.2018
comment
Все, что я получаю, это: не могу найти имя «процесс». - person Eric; 03.04.2021

Стеки событий Javascript работают не так, как вы думаете.

Когда создается событие тайм-аута, оно добавляется в очередь событий, но другие события могут иметь приоритет, пока это событие запускается, задерживая время выполнения и откладывая время выполнения.

Пример:. Вы создаете тайм-аут с задержкой в ​​10 секунд, чтобы что-то предупредить на экране. Он будет добавлен в стек событий и будет выполняться после запуска всех текущих событий (вызывая некоторую задержку). Затем, когда истечет время ожидания, браузер по-прежнему продолжает перехватывать другие события, добавляя их в стек, что вызывает дополнительные задержки в обработке. Если пользователь щелкает или набирает много клавиш ctrl +, их события имеют приоритет над текущим стеком. Ваши 10 секунд могут превратиться в 15 секунд или больше.


При этом есть много способов подделать, сколько времени прошло. Один из способов - выполнить setInterval сразу после добавления setTimeout в стек.

Пример: Выполните установку тайм-аута с 10-секундной задержкой (сохраните эту задержку в глобальном). Затем выполните setInterval, который запускается каждую секунду, чтобы вычесть 1 из задержки и вывести оставшуюся задержку. Из-за того, как стек событий может влиять на фактическое время (описано выше), это все равно не будет точным, но дает счет.


Короче говоря, реального способа получить оставшееся время нет. Есть только способы попытаться донести оценку до пользователя.

person vol7ron    schedule 30.06.2010

Вы можете изменить setTimeout, чтобы сохранить время окончания каждого тайм-аута на карте, и создать функцию под названием getTimeout, чтобы получить время, оставшееся до тайм-аута с определенным идентификатором.

Это было супер решение, но я изменил его, чтобы использовать немного меньше памяти

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

Надеюсь, это будет так же полезно для вас, как и для меня! :)

person Kimeiga    schedule 22.08.2019

Нет, но у вас может быть собственный 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 );
}
person gblazex    schedule 29.06.2010
comment
ну, разницу можно измерить только в мс, потому что анимация начинается в верхней части вашей функции. Я видел, как вы используете jQuery. Тогда вы можете использовать jquery.animate. - person gblazex; 30.06.2010
comment
Лучший способ - попробовать и убедиться в этом самому. :) - person gblazex; 30.06.2010

Если кто-то оглядывается на это. Я выпустил диспетчер тайм-аута и интервалов, который может дать вам время, оставшееся до тайм-аута или интервала, а также сделать некоторые другие вещи. Я добавлю к нему, чтобы сделать его более изящным и точным, но, похоже, он и так работает достаточно хорошо (хотя у меня есть еще несколько идей, чтобы сделать его еще более точным):

https://github.com/vhmth/Tock

person Vinay    schedule 03.11.2012

На вопрос уже был дан ответ, но я добавлю свой бит. Это просто произошло со мной.

Используйте setTimeout в recursion следующим образом:

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

Я думаю, код не требует пояснений

person dasfdsa    schedule 25.10.2016

Отметьте это:

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()
person crokudripi    schedule 31.01.2017

Я зашел сюда в поисках ответа, но слишком много думал о своей проблеме. Если вы здесь, потому что вам просто нужно отслеживать время, пока выполняется 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);
person Jbur43    schedule 22.12.2018

Более быстрый и простой способ:

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

Вы можете узнать оставшееся время с помощью:

 timeLeft = tmo - (performance.now() - start);
person Danial    schedule 10.08.2020