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)] = {
        開始:新しいDate()。getTime()、
        終了:新しいDate()。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);

これは製品コードではありませんが、正しい方向に進むはずです。タイムアウトごとにバインドできるリスナーは1つだけであることに注意してください。私はこれで大規模なテストを行っていませんが、Firebugで動作します。

より堅牢なソリューションでは、setTimeoutをラップするのと同じ手法を使用しますが、代わりに、返されたtimeoutIdからリスナーへのマップを使用して、タイムアウトごとに複数のリスナーを処理します。タイムアウトがクリアされた場合にリスナーを切り離すことができるように、clearTimeoutをラップすることを検討することもできます。


ありがとうあなたは私の日を救った!
xecutioner 2014年

14
実行時間のため、これはしばらくの間数ミリ秒ずれることがあります。これを行うためのより安全な方法は、タイムアウトを開始した時刻を記録し(new Date())、それを現在の時刻から減算することです(別の新しいDate())
Jonathan

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 2013年

3
私はノード0.10で試してみましたgetTime()が、削除されました。_idleStartもうその時です
Tan Nguyen

2
タイムアウト構造はこれらの情報のリークであるため、ノード6.3では機能しません。
Huan 2016

53

編集:私は実際に私がさらに良いものを作ったと思います: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秒以上になります。


そうは言っても、どれだけの時間が経過したかを偽造する方法はたくさんあります。1つの方法は、setTimeoutをスタックに追加した直後にsetIntervalを実行することです。

例: 10秒の遅延でsettimeoutを実行します(その遅延をグローバルに保存します)。次に、毎秒実行される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+でrefreshtimerを使用してオブジェクトをジャグリングするのは興味深いかもしれません。


1

あなたは、することができます変更 setTimeoutマップ内の各タイムアウトの終了時刻を格納し、呼び出された関数を作成するためにgetTimeout、特定のIDを持つタイムアウトのために残された時間を取得するために。

これはスーパーソリューションでしたが、少し少ないメモリを使用するように変更しました

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 2010年

最善の方法は、自分で試してみることです。:)
gblazex 2010年

0

誰かがこれを振り返っているなら。タイムアウトまたはインターバルの残り時間を取得したり、他の処理を実行したりできるタイムアウトおよびインターバルマネージャーを用意しました。私はそれをより気の利いた、より正確にするために追加しますが、それはかなりうまく機能しているようです(私はそれをさらに正確にするためのいくつかのアイデアがありますが):

https://github.com/vhmth/Tock


あなたのリンクが壊れています
キック・ブットウスキー2015

0

質問はすでに回答済みですが、少し追加します。それは私に起こったばかりです。

次のように使用setTimeoutrecursionます。

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);
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.