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

91

Я пишу некоторый 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
function mySetTimeout (func, timeout) {
    таймауты [n = setTimeout (func, timeout)] = {
        начало: новая дата (). getTime (),
        конец: новая дата (). getTime () + тайм-аут
        t: тайм-аут
    }
    return n;
}

Это прекрасно работает в любом браузере, кроме IE 6. Даже в оригинальном iPhone, где я ожидал, что все будет асинхронным.

Просто Джейк
источник

Ответы:

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
15
Из-за времени выполнения это может измениться на несколько миллисекунд в течение некоторого времени. Более безопасный способ сделать это - записать время, когда вы начали тайм-аут (new Date ()), а затем просто вычесть его из текущего времени (еще один новый Date ())
Джонатан
62

Для записи, есть способ узнать оставшееся время в 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.

Пушистый
источник
5
не уверен, что это новая версия узла или что-то еще, но для того, чтобы это работало, мне пришлось удалить часть "getTime ()". кажется, _idleStart уже является целым числом
kasztelan
3
Я только что попробовал в узле 0.10, getTime()удалено, _idleStartуже время
Тан Нгуен
2
не работает на узле 6.3, потому что структура тайм-аута является утечкой этой информации.
Huan
54

РЕДАКТИРОВАТЬ: Я действительно думаю, что сделал еще лучше: 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 секунд или больше.


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

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


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

vol7ron
источник
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+.

Джон Уоттс
источник
1

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

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

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

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

https://github.com/vhmth/Tock

Vinay
источник
0

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

Используйте 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");
}

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

dasfdsa
источник
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);
Виталик s2pac
источник
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);
Jbur43
источник
0

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

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

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

 timeLeft = tmo - (performance.now() - start);
Даниал
источник