encontrar o tempo restante em um setTimeout ()?

91

Estou escrevendo um Javascript que interage com o código da biblioteca que não possuo e não pode (razoavelmente) alterar. Ele cria tempos limite de Javascript usados ​​para mostrar a próxima pergunta em uma série de perguntas com limite de tempo. Este não é um código real porque está ofuscado além de toda esperança. Aqui está o que a biblioteca está fazendo:

....
// 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 );

Quero colocar uma barra de progresso na tela que preenche questionTime * 1000interrogando o cronômetro criado por setTimeout. O único problema é que parece não haver maneira de fazer isso. Existe uma getTimeoutfunção que estou perdendo? A única informação sobre tempos limite de Javascript que posso encontrar está relacionada apenas à criação via setTimeout( function, time)e exclusão via clearTimeout( id ).

Estou procurando uma função que retorna o tempo restante antes que um tempo limite seja acionado ou o tempo decorrido após a chamada de um tempo limite. Meu código de barras de progresso é assim:

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: Como encontro o tempo restante antes de um setTimeout () javascript?


Aqui está a solução que estou usando agora. Passei pela seção da biblioteca responsável pelos testes e desembaralhei o código (terrível e contra minhas permissões).

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

e aqui está meu código:

// wrapper para setTimeout
function mySetTimeout (func, timeout) {
    tempos limite [n = setTimeout (func, tempo limite)] = {
        start: new Date (). getTime (),
        fim: nova data (). getTime () + tempo limite
        t: tempo limite
    }
    return n;
}

Isso funciona muito bem em qualquer navegador que não seja o IE 6. Até mesmo no iPhone original, onde eu esperava que as coisas ficassem assíncronas.

Apenas Jake
fonte

Respostas:

31

Se você não pode modificar o código da biblioteca, você precisará redefinir setTimeout para atender às suas finalidades. Aqui está um exemplo do que você pode fazer:

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

Este não é um código de produção, mas deve colocá-lo no caminho certo. Observe que você só pode vincular um ouvinte por tempo limite. Não fiz testes extensivos com isso, mas funciona no Firebug.

Uma solução mais robusta usaria a mesma técnica de agrupar setTimeout, mas, em vez disso, usaria um mapa do timeoutId retornado para ouvintes para lidar com vários ouvintes por tempo limite. Você também pode considerar agrupar clearTimeout para que possa desconectar seu ouvinte se o tempo limite for limpo.

gramado
fonte
Obrigado. Você salvou meu dia!
xecutioner de
15
Devido ao tempo de execução, isso pode variar alguns milissegundos durante algum tempo. Uma maneira mais segura de fazer isso é registrar a hora em que você iniciou o tempo limite (nova data ()) e depois apenas subtraí-la da hora atual (outra nova data ())
Jonathan
62

Apenas para registro, há uma maneira de obter o tempo restante em 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);
}

Impressões:

$ node test.js 
Time left: 3599s
Time left: 3597s
Time left: 3595s
Time left: 3593s

Isso não parece funcionar no firefox, mas como node.js é javascript, achei que essa observação poderia ser útil para pessoas que procuram a solução de nó.

Fofo
fonte
5
não tenho certeza se é uma nova versão do nó ou o quê, mas para que isso funcione eu tive que remover a parte "getTime ()". parece que _idleStart já é um número inteiro agora
kasztelan
3
Acabei de experimentar no nó 0.10, getTime()foi removido, _idleStartjá é a hora
Tan Nguyen
2
não funciona no nó 6.3, porque a estrutura de tempo limite é o vazamento dessas informações.
Huan
54

EDIT: Na verdade, acho que fiz um ainda melhor: https://stackoverflow.com/a/36389263/2378102

Escrevi esta função e a uso muito:

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

Faça um cronômetro:

a = new timer(function() {
    // What ever
}, 3000)

Então, se você quiser o tempo restante, basta fazer:

a.getTimeLeft()
Comunidade
fonte
Obrigado por isso. Não é exatamente o que estou procurando, mas a função de contar o tempo restante é muito útil
vez disso,
4

As pilhas de eventos do Javascript não funcionam como você pensa.

Quando um evento de tempo limite é criado, ele é adicionado à fila de eventos, mas outros eventos podem ter prioridade enquanto aquele evento está sendo disparado, atrasando o tempo de execução e postergando o tempo de execução.

Exemplo: você cria um tempo limite com um atraso de 10 segundos para alertar algo na tela. Ele será adicionado à pilha de eventos e executado depois que todos os eventos atuais forem disparados (causando algum atraso). Então, quando o tempo limite é processado, o navegador ainda continua a capturar outros eventos e adicioná-los à pilha, o que causa mais atrasos no processamento. Se o usuário clicar ou digitar muito ctrl +, seus eventos terão prioridade sobre a pilha atual. Seus 10 segundos podem se transformar em 15 segundos ou mais.


Dito isso, há muitas maneiras de fingir quanto tempo se passou. Uma maneira é executar um setInterval logo depois de adicionar setTimeout à pilha.

Exemplo: Execute um settimeout com um retardo de 10 segundos (armazene esse retardo em um global). Em seguida, execute um setInterval que é executado a cada segundo para subtrair 1 do atraso e gerar o atraso restante. Por causa de como a pilha de eventos pode influenciar o tempo real (descrito acima), isso ainda não será preciso, mas fornece uma contagem.


Resumindo, não há uma maneira real de obter o tempo restante. Existem apenas maneiras de tentar transmitir uma estimativa ao usuário.

Vol7ron
fonte
4

Específico para Node.js do lado do servidor

Nenhuma das opções acima realmente funcionou para mim e, depois de inspecionar o objeto de tempo limite, parecia que tudo era relativo a quando o processo começou. O seguinte funcionou para mim:

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

Resultado:

14
...
3
2
1
Timer executed
-0
-1
...

node -v
v9.11.1

Saída editada por questões de brevidade, mas esta função básica fornece um tempo aproximado até a execução ou desde a execução. Como outros mencionaram, nada disso será exato devido à forma como o nó é processado, mas se eu quiser suprimir uma solicitação que foi executada há menos de 1 minuto e armazenei o cronômetro, não vejo por que isso não funcionam como uma verificação rápida. Pode ser interessante fazer malabarismos com objetos com o refreshtimer na versão 10.2+.

John Watts
fonte
1

Você pode modificar setTimeout para armazenar o tempo de término de cada timeout em um mapa e criar uma função chamadagetTimeout para obter o tempo restante para um timeout com um determinado id.

Esta foi a solução do super , mas eu a modifiquei para usar um pouco menos de memória

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

Uso:

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

Aqui está uma versão getTimeout reduzida deste 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})();

Espero que isso seja tão útil para você quanto foi para mim! :)

Kimeiga
fonte
0

Não, mas você pode ter seu próprio setTimeout / setInterval para animação em sua função.

Digamos que sua pergunta seja assim:

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

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

E sua animação será controlada por estas 2 funções:

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
fonte
bem a diferença só pode ser medida em ms, porque a animação começa no topo da sua função. Eu vi você usar jQuery. Você pode usar jquery.animate então.
gblazex
A melhor maneira é tentar ver por si mesmo. :)
gblazex
0

Se alguém está olhando para trás. Eu criei um gerenciador de tempo limite e intervalo que pode obter o tempo restante em um tempo limite ou intervalo, bem como fazer outras coisas. Vou acrescentá-lo para torná-lo mais bacana e preciso, mas parece funcionar muito bem como está (embora eu tenha mais algumas ideias para torná-lo ainda mais preciso):

https://github.com/vhmth/Tock

Vinay
fonte
0

A pergunta já foi respondida, mas vou acrescentar minha parte. Apenas ocorreu para mim.

Use setTimeoutda recursionseguinte maneira:

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

Eu acho que o código é autoexplicativo

dasfdsa
fonte
0

Verifique este:

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

Faça um cronômetro:

let smtg = new Timer(()=>{do()}, 3000})

Obtenha permanecer:

smth.get()

Limpe o tempo limite

smth.clear()
Crokudripi
fonte
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);
Vitalik s2pac
fonte
0

Parei aqui procurando essa resposta, mas estava pensando demais no meu problema. Se você está aqui porque precisa apenas de controlar o tempo enquanto define o tempo, esta é outra maneira de fazer isso:

    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
fonte
0

Uma maneira mais rápida e fácil:

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

Você pode obter o tempo restante com:

 timeLeft = tmo - (performance.now() - start);
Danial
fonte