หาเวลาที่เหลือใน setTimeout ()?


91

ฉันกำลังเขียน 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 สำหรับ setTimeout
ฟังก์ชัน mySetTimeout (func, timeout) {
    หมดเวลา [n = setTimeout (func, หมดเวลา)] = {
        เริ่มต้น: วันที่ใหม่ (). 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

15
เนื่องจากเวลาดำเนินการสิ่งนี้อาจลอยไปไม่กี่มิลลิวินาทีในช่วงเวลาหนึ่ง วิธีที่ปลอดภัยกว่าในการทำเช่นนี้คือบันทึกเวลาที่คุณเริ่มการหมดเวลา (วันที่ใหม่ ()) แล้วลบออกจากเวลาปัจจุบัน (วันที่ใหม่ ())
โจนาธาน

62

สำหรับเร็กคอร์ดมีวิธีรับเวลาที่เหลือใน 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แล้วเป็นเวลาแล้ว
Tan Nguyen

2
ไม่ทำงานบนโหนด 6.3 เนื่องจากโครงสร้างการหมดเวลารั่วไหลของข้อมูลเหล่านั้น
เฮือน

54

แก้ไข: ฉันคิดว่าฉันทำได้ดีกว่านี้จริงๆ: 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 ลงในสแต็ก

ตัวอย่าง:ดำเนินการ 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เพื่อให้เวลาที่เหลือสำหรับการหมดเวลาโดยใช้รหัสที่กำหนด

นี่คือสุดยอดของการแก้ปัญหาแต่ผมปรับเปลี่ยนไปใช้หน่วยความจำน้อย

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


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);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.