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

11 72

11 ответов:

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

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

EDIT: я на самом деле думаю, что я сделал еще лучше: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()

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

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

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


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

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


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

вот, возможно, еще лучший способ сделать это, плюс, вам не нужно будет менять код, который вы уже написали:

var getTimeout = (function() { // IIFE
    var _setTimeout = setTimeout, // Reference to the original setTimeout
        map = {}; // Map of all timeouts with their start date and delay

    setTimeout = function(callback, delay) { // Modify setTimeout
        var id = _setTimeout(callback, delay); // Run the original, and store the id

        map[id] = [Date.now(), delay]; // Store the start date and delay

        return id; // Return the id
    };

    return function(id) { // The actual getTimeLeft function
        var m = map[id]; // Find the timeout in map

        // If there was no timeout with that id, return NaN, otherwise, return the time left clamped to 0
        return m ? Math.max(m[1] - Date.now() + m[0], 0) : NaN;
    }
})();

... И передразнил:

var getTimeout=function(){var e=setTimeout,b={};setTimeout=function(a,c){var d=e(a,c);b[d]=[Date.now(),c];return d};return function(a){return(a=b[a])?Math.max(a[1]-Date.now()+a[0],0):NaN}}();

узел на стороне сервера.конкретные Яш

ни один из вышеперечисленных действительно не работал для меня, и после проверки объекта timeout казалось, что все было относительно того, когда начался процесс. Для меня сработало следующее:

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

отредактированный вывод для краткости, но эта основная функция дает приблизительное время до выполнения или с момента выполнения. Как упоминают другие, ничто из этого не будет точным из-за узла way но если я хочу подавить запрос, который был запущен менее 1 минуты назад, и я сохранил таймер, я не вижу, почему это не будет работать как быстрая проверка. Может быть интересно жонглировать объектами с refreshtimer в 10.2+.

нет, но вы можете иметь свой собственный setTimeout/setInterval для анимации в вашей функции.

скажите, что ваш вопрос выглядит так:

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

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

и ваша анимация будет обрабатываться этими 2 функциями:

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

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

https://github.com/vhmth/Tock

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

использовать setTimeout in 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");
}

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

проверьте этот:

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

Get remain:

smth.get()

очистить тайм-аут

smth.clear()
    (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);