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

Ответ 1

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

Ответ 2

Только для записи есть способ получить оставшееся время в 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.

Ответ 3

EDIT: Я действительно думаю, что сделал еще лучше: fooobar.com/questions/87148/...

Я написал эту функцию, и я использую ее много:

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 + typing, их события имеют приоритет над текущим стеком. Ваши 10 секунд могут превратиться в 15 секунд или дольше.


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

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


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

Ответ 5

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

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

Ответ 6

Специфичный 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+.

Ответ 7

Нет, но у вас может быть свой собственный 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 );
}

Ответ 8

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

https://github.com/vhmth/Tock

Ответ 9

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

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

Я думаю, что код сам по себе

Ответ 10

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

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 оставаться:

smth.get()

Удалить тайм-аут

smth.clear()

Ответ 11

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

Ответ 12

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

Ответ 13

Вы можете изменить 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})();

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