setTimeout ใน javascript ไม่ให้เบราว์เซอร์ 'ห้องหายใจ'

เอาล่ะ ฉันคิดว่าฉันมี setTimeout ทั้งหมดนี้สมบูรณ์แบบแล้ว แต่ดูเหมือนว่าฉันจะคิดผิดอย่างมหันต์

ฉันใช้ excanvas และ javascript เพื่อวาดแผนที่สถานะบ้านเกิดของฉัน อย่างไรก็ตาม ขั้นตอนการวาดทำให้เบราว์เซอร์ใช้งานไม่ได้ ตอนนี้ฉันถูกบังคับให้หันไปใช้ IE6 เพราะฉันอยู่ในองค์กรขนาดใหญ่ ซึ่งอาจเป็นส่วนสำคัญของการทำงานช้า

ดังนั้นสิ่งที่ฉันคิดว่าจะทำคือสร้างขั้นตอนที่เรียกว่า distributionDrawPolys (ฉันอาจใช้คำผิดตรงนั้น ดังนั้นอย่าไปเน้นที่คำที่กระจาย) ซึ่งโดยพื้นฐานแล้วจะทำให้รูปหลายเหลี่ยมหลุดออกจากอาเรย์ส่วนกลางเพื่อที่จะดึง 50 ของพวกเขาในแต่ละครั้ง

นี่เป็นวิธีที่ส่งรูปหลายเหลี่ยมไปยังอาเรย์ส่วนกลางและรัน setTimeout:

 for (var x = 0; x < polygon.length; x++) {
      coordsObject.push(polygon[x]);
      fifty++;
      if (fifty > 49) {
           timeOutID = setTimeout(distributedDrawPolys, 5000);
           fifty = 0;
      }
 }

ฉันใส่การแจ้งเตือนไว้ที่ส่วนท้ายของวิธีการนั้น มันจะทำงานได้ในไม่กี่วินาที

วิธีการกระจายมีลักษณะดังนี้:

 function distributedDrawPolys()
 {
      if (coordsObject.length > 0) {
           for (x = 0; x < 50; x++) { //Only do 50 polygons
                var polygon = coordsObject.pop();
                var coordinate = polygon.selectNodes("Coordinates/point");
                var zip = polygon.selectNodes("ZipCode");
                var rating = polygon.selectNodes("Score");
                if (zip[0].text.indexOf("HH") == -1) {
                     var lastOriginCoord = [];
                     for (var y = 0; y < coordinate.length; y++) {
                          var point = coordinate[y];
                          latitude = shiftLat(point.getAttribute("lat"));
                          longitude = shiftLong(point.getAttribute("long"));
                          if (y == 0) {
                               lastOriginCoord[0] = point.getAttribute("long");
                               lastOriginCoord[1] = point.getAttribute("lat");
                          }
                          if (y == 1) {
                               beginPoly(longitude, latitude);
                          }
                          if (y > 0) {
                               if (translateLongToX(longitude) > 0 && translateLongToX(longitude) < 800 && translateLatToY(latitude) > 0 && translateLatToY(latitude) < 600) {
                                    drawPolyPoint(longitude, latitude);
                               }
                          }
                     }
                     y = 0;
                     if (zip[0].text != targetZipCode) {
                          if (rating[0] != null) {
                               if (rating[0].text == "Excellent") {
                                    endPoly("rgb(0,153,0)");
                               }
                               else if (rating[0].text == "Good") {
                                    endPoly("rgb(153,204,102)");
                               }
                               else if (rating[0].text == "Average") {
                                    endPoly("rgb(255,255,153)");
                               }
                          }
                          else { endPoly("rgb(255,255,255)"); }
                     }
                     else {
                     endPoly("rgb(255,0,0)");
                     }
                }
           }
      }
 }

แก้ไข: แก้ไขรูปแบบแล้ว

ดังนั้นฉันคิดว่าเมธอด setTimeout จะทำให้ไซต์วาดรูปหลายเหลี่ยมเป็นกลุ่มได้ ดังนั้นผู้ใช้จึงสามารถโต้ตอบกับเพจได้ในขณะที่ยังวาดรูปอยู่ ฉันทำอะไรผิดที่นี่?


person C Bauer    schedule 16.03.2010    source แหล่งที่มา


คำตอบ (5)


เปลี่ยนรหัสเป็น

for (var x = 0; x < polygon.length; x++) {
    coordsObject.push(polygon[x]);
}
distributedDrawPolys();

function distributedDrawPolys()
{
    if (coordsObject.length > 0) {
        for (x = 0; x < 50; x++) {
            ...
        }
        setTimeout("distributedDrawPolys()", 5000); //render next 50 polys in 5 sec
    }
}
person jitter    schedule 16.03.2010
comment
เบราว์เซอร์ยังคงล็อคอยู่เมื่อฉันเรียกใช้โค้ดเช่นนี้ :/ (ฉันยังใส่ settimeout ไว้ในคำสั่ง if ด้วย) - person C Bauer; 16.03.2010
comment
@jitter: คุณไม่จำเป็นต้องหรือต้องการใส่ฟังก์ชันในเครื่องหมายคำพูด (หลีกเลี่ยงรูปแบบ eval ทุกรูปแบบเมื่อทำได้ รวมถึงรูปแบบโดยนัยด้วย) setTimeout ที่เหมาะสมจะเป็น: setTimeout(distributedDrawPolys, 5000); (โปรดทราบว่าไม่ได้อยู่ในเครื่องหมายคำพูด และ ไม่มี มี () ตามหลัง -- มันคือฟังก์ชัน การอ้างอิงที่เราต้องการ ไม่ใช่ค่าตอบแทนของฟังก์ชัน) - person T.J. Crowder; 16.03.2010
comment
@C Bauer: setTimeout ไม่ได้บล็อก แต่กำหนดเวลาการดำเนินการล่าช้าเท่านั้น ความแตกต่างในตัวอย่างนี้เมื่อเปรียบเทียบกับของคุณ คือคุณกำหนดเวลาการโทรทั้งหมดให้เริ่มต้นหลังจาก 5 วินาที ในขณะที่ฟังก์ชันตัวอย่าง jitter จะทำงานเองหลังจาก 5 วินาที ดังนั้นมันจึงทำหนึ่งพวง จากนั้นกำหนดเวลาให้รันเองหลังจากผ่านไป 5 วินาทีสำหรับอีกพวง ฯลฯ - person awe; 16.03.2010
comment
@jitter: ใช่ ฉันรู้และเข้าใจ ฉันแค่ตอบความคิดเห็นแรกของเขาที่นี่เกี่ยวกับการบอกว่าเบราว์เซอร์ล็อคอยู่ - person awe; 16.03.2010

หากการวนซ้ำของคุณทำงานภายในเสี้ยววินาที การโทร setTimeout ทั้งหมดของคุณจะซ้อนกันโดยพยายามเริ่มทำงานในอีกประมาณห้าวินาทีต่อมา

หากคุณต้องการให้เบราว์เซอร์มีพื้นที่ว่างสำหรับการเรนเดอร์ระดับกลาง ให้พุชอ็อบเจ็กต์ทั้งหมดของคุณบนสแต็ก จากนั้นเรียกใช้ฟังก์ชันแบบมีขีดจำกัด และกำหนดให้ฟังก์ชันกำหนดเวลาเองเมื่อทำอ็อบเจ็กต์จำนวนมากขนาดนั้น กึ่งรหัสเทียม:

var theArray = [];
var limit = 50;

function showStuff() {
    for (...) {
        // push objects on theArray
    }

    renderArrayInBatches();
}

function renderArrayInBatches() {
    var counter;

    for (counter = limit; counter > 0; --counter) {
        // pop an object and render it
    }
    if (theArray.length > 0) {
        setTimeout(renderArrayInBatches, 10);
    }
}

ซึ่งจะสร้างอาร์เรย์ทั้งหมดในคราวเดียว จากนั้นทริกเกอร์ชุดแรก (สูงสุด limit) ของการแสดงผล ในตอนท้ายของชุดแรก หากมีการเรนเดอร์ที่ต้องทำมากกว่านี้ ระบบจะกำหนดเวลาให้เกิดขึ้นประมาณ 10 มิลลิวินาทีในภายหลัง ในความเป็นจริง มันจะเกิดขึ้นไม่เร็วกว่าใน 10 มิลลิวินาทีในภายหลัง และอาจช้ากว่านั้นหากเบราว์เซอร์ยังคงยุ่งอยู่กับสิ่งอื่น (อีก 10 มิลลิวินาที: เบราว์เซอร์ส่วนใหญ่จะไม่กำหนดเวลาบางอย่างให้เร็วกว่า 10 มิลลิวินาทีนับจากนี้) (แก้ไข Andy E ชี้ให้เห็นอย่างถูกต้องว่าคุณอาจพับตรรกะที่เกี่ยวข้องกับสิ่งที่จำเป็นต้องเป็นได้เช่นกัน แสดงผลเป็นฟังก์ชันการเรนเดอร์โดยตรงแทนที่จะสร้างอาร์เรย์ก่อนแล้วจึงประมวลผล ไม่ได้เปลี่ยนแปลงสิ่งที่กล่าวมาข้างต้นมากนักยกเว้นส่วนของอาร์เรย์ วิธีที่คุณทำการผูกมัดและ "ห้องหายใจ" ยังคงเหมือนเดิม)

คุณอาจพบว่าคุณจำเป็นต้องปรับเวลาหมดเวลาโดยไม่ทราบถึงสิ่ง excanvas ที่คุณใช้อยู่ แต่ฉันมักจะสงสัย -- โดยพื้นฐานแล้วมันเป็นการดำเนินการ "ให้ผลตอบแทน" ซึ่งช่วยให้เบราว์เซอร์ทำบางสิ่งและกลับมาหาคุณ

โปรดทราบว่าตัวอย่างโค้ดหลอกด้านบนใช้สิ่งที่ดูเหมือนจะเป็นโกลบอล ฉันไม่แนะนำให้ใช้ globals จริงๆ คุณอาจต้องการทำสิ่งนี้แทน:

function showStuff() {
    var theArray = [];
    var limit = 50;

    for (...) {
        // push objects on theArray
    }

    renderArrayInBatches();

    function renderArrayInBatches() {
        var counter;

        for (counter = limit; counter > 0; --counter) {
            // pop an object and render it
        }
        if (theArray.length > 0) {
            setTimeout(renderArrayInBatches, 10);
        }
    }
}

...แต่ฉันไม่ชอบทำให้คำตอบหลักซับซ้อนด้วยการแนะนำการปิด (แม้ว่าในทางเทคนิคแล้ว codeblock ทั้งสองจะเกี่ยวข้องกับการปิด)

person T.J. Crowder    schedule 16.03.2010
comment
คำตอบที่ดี @C Bauer ต้องจำไว้ว่า setTimeout ไม่ได้บล็อก เพียงกำหนดเวลาให้ฟังก์ชันทำงานในอนาคต ดังนั้นการหมดเวลาจึงสามารถซ้อนกันได้ - person Jonathon Faust; 16.03.2010
comment
ฉันกำลังพิมพ์คำตอบที่คล้ายกัน ดังนั้น +1 อีกทางเลือกหนึ่งคือเลิกใช้ for loop และให้การโทร distributedDrawPolys() แต่ละครั้งตั้งเวลาสำหรับครั้งถัดไป โดยห่างกันประมาณ 10ms - person Andy E; 16.03.2010
comment
สวัสดี TJ ฉันแค่อยากจะบอกว่าฉันซาบซึ้งกับความพยายามที่คุณใส่ไว้ในคำตอบนี้และเมื่อฉันเข้าใจสิ่งที่ฉันสับสนกับ setTimeout ในตอนแรก (ส่วนหนึ่งเนื่องจากความคิดเห็นของ Andy E) มันก็สมเหตุสมผลมากขึ้น . อย่างไรก็ตาม ฉันคงไปไม่ถึงจุดนั้นจริงๆ หากไม่มีตัวอย่างของความกระวนกระวายใจ - person C Bauer; 16.03.2010

ไม่ คุณต้องการสิ่งที่แตกต่างออกไป

var batchsize = 50; 
function drawPolys(start) {
    for (var x = start; x < polygon.length; x++) {
        drawOnePolygon(polygon[x]);
        if (start + batchsize <= x) {
            // quit this invocation, and schedule the next
            setTimeout(function(){drawPolys(x+1);}, 400);
            return;  // important
        }
    }
}

ดังนั้น DrawOnePolygon จะต้องเป็นดังนี้:

function drawOnePolygon(p) {
    var coordinate = polygon.selectNodes("Coordinates/point");
    //...and so on, continuing from your code above.
}

เริ่มต้นด้วย:

drawPolys(0); 
person Cheeso    schedule 16.03.2010

หากคุณเรียกมันทุกๆ ห้าวินาที และทำงาน 1 วินาทีในแต่ละครั้ง เบราว์เซอร์จะถูกปิดกั้นสำหรับการโต้ตอบ 20% ของเวลา

คุณสามารถลองสับฟังก์ชันใหญ่ๆ ของคุณออกแล้วรันเป็นชิ้นๆ เพื่อให้ประสบการณ์ราบรื่นยิ่งขึ้น

person npup    schedule 16.03.2010

สิ่งนี้ไม่ทำงานอย่างที่คาดไว้ เมื่อถึงเวลาที่ฟังก์ชันแรกของคุณเริ่มทำงาน อาร์เรย์ส่วนกลางของคุณจะมีองค์ประกอบ 50 รายการแล้ว คุณเพียงแค่ใช้งานข้อมูลเดียวกัน 50 ครั้ง

สิ่งที่คุณสามารถทำได้คือเชื่อมโยง setTimeout ของคุณเพื่อให้ฟังก์ชันถัดไปดำเนินการหลังจากวิธีก่อนหน้า

ต่อไปนี้เป็นการใช้งานแบบง่ายๆ:

var coordObj = [...]; //50 or whatever elements
(function() {
    if (coordObj.length === 0) return; //Guardian
    var obj = coordObj.pop(); //or .shift(), based on the order desired.
    doStuffWithCoordObj(obj);
    setTimeout(arguments.callee,0); //call this anonymous function after a timeout
})();
person Chetan S    schedule 16.03.2010
comment
วิธีการ pop จะลบอ็อบเจ็กต์อาร์เรย์ที่ส่งคืนออกจากโกลบอลทุกครั้งที่มีการรัน ดังนั้นข้อมูลจะไม่ถูกนำมาใช้ซ้ำ - person C Bauer; 16.03.2010
comment
@C Bauer: เยี่ยมมาก ฉันไม่ได้ดูฟังก์ชั่นนั้นเลย ไม่ว่าในกรณีใด นี่ไม่ใช่วิธีแก้ปัญหา มันเป็นเพียงรูปแบบสำหรับการอัปเดต UI แบบไม่บล็อกแบบยาว - person Chetan S; 16.03.2010