знайти час, що залишився в setTimeout ()?


90

Я пишу 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
функція mySetTimeout (func, timeout) {
    таймаути [n = setTimeout (func, timeout)] = {
        start: new Date (). getTime (),
        кінець: нова дата (). getTime () + час очікування
        t: час очікування
    }
    повернути 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

14
Через час виконання, це може дрейфувати на кілька мілісекунд протягом деякого часу. Більш безпечний спосіб зробити це - записати час, коли ви почали час очікування (нова дата ()), а потім просто відняти його від поточного часу (інша нова дата ())
Джонатан

61

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


5
не впевнений, що це нова версія вузла чи що, але для того, щоб це працювало, мені довелося видалити частину "getTime ()". здається _idleStart вже ціле число
kasztelan

3
Я щойно спробував у вузлі 0.10, getTime()видалено, _idleStartце вже час
Тан Нгуєн

2
не працює на вузлі 6.3, оскільки структура часу очікування витікає з цієї інформації.
Хуан,

53

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

Дякую за це. Не зовсім те, що я шукав, але функція підрахунку залишку часу дуже корисна
натомість

4

Стеки подій Javascript не працюють так, як ви думаєте.

Коли подія тайм-ауту створена, вона додається до черги подій, але інші події можуть мати пріоритет під час запуску цієї події, затримати час виконання та відкласти час виконання.

Приклад: Ви створюєте тайм-аут із затримкою 10 секунд, щоб щось сповістити на екран. Він буде доданий до стеку подій і буде виконаний після запуску всіх поточних подій (спричиняючи деяку затримку). Потім, коли тайм-аут обробляється, браузер все ще продовжує фіксувати інші події, додаючи їх у стек, що спричиняє подальші затримки в обробці. Якщо користувач натискає або робить багато ctrl + набравши текст, його події мають пріоритет над поточним стеком. Ваші 10 секунд можуть перетворитися на 15 секунд або довше.


З огляду на це, існує багато способів підробити, скільки часу минуло. Один із способів - виконати setInterval відразу після того, як ви додасте setTimeout у стек.

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


Коротше кажучи, немає реального способу отримати час, що залишився. Є лише способи спробувати донести оцінку до користувача.


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
  // ...
}

І вашою анімацією будуть керувати ці 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 );
}

ну різницю можна виміряти лише у мс, оскільки анімація починається у верхній частині вашої функції. Я бачив, як ви використовуєте jQuery. Тоді ви можете використовувати jquery.animate.
gblazex

Найкращий спосіб - спробувати і переконатися в цьому на власні очі. :)
gblazex

0

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

https://github.com/vhmth/Tock


ваші посилання недійсні
Kick Buttowski

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

Я думаю, код сам собою пояснює


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

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

0

Швидший і простіший спосіб:

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

Ви можете отримати час, що залишився за допомогою:

 timeLeft = tmo - (performance.now() - start);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.