setTimeout в javascript не дает браузеру «передышку»

Хорошо, я думал, что все это setTimeout идеально, но я, кажется, ужасно ошибался.

Я использую excanvas и javascript, чтобы нарисовать карту своего родного штата, однако процедура рисования блокирует браузер. Прямо сейчас я вынужден потакать IE6, потому что я работаю в большой организации, что, вероятно, является большой частью медлительности.

Итак, я решил создать процедуру под названием «распределенныйDrawPolys» (вероятно, я использую неправильное слово, так что не обращайте внимания на слово «распределенный»), которая, по сути, выталкивает полигоны из глобального массива, чтобы отрисовать 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, включая неявные). ссылку, которую мы хотим, а не возвращаемое значение функции). - 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 мс.) (Правка. Энди Э. совершенно правильно отмечает, что вы можете также свернуть логику, связанную с тем, что должно быть визуализируется в функцию рендеринга напрямую, а не сначала строит массив, а затем обрабатывает его. Не сильно меняет вышеизложенное, за исключением части массива, то, как вы выполняете цепочку, и «комната для дыхания» остается прежней.)

Не зная, что вы используете в excanvas, вы можете обнаружить, что вам нужно настроить время ожидания, но я склонен сомневаться в этом - это в основном операция «выход», которая позволяет браузеру делать некоторые вещи и возвращаться к вам.

Обратите внимание, что приведенный выше пример псевдокода использует то, что кажется глобальным. Я бы не рекомендовал использовать глобальные переменные. Возможно, вы даже захотите сделать это вместо этого:

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

... но мне не хотелось усложнять основной ответ, вводя закрытие (хотя технически оба блока кода включают закрытие).

person T.J. Crowder    schedule 16.03.2010
comment
Хороший ответ. @C Бауэр, нужно помнить, что setTimeout не блокирует. Он просто планирует запуск функции на какое-то время в будущем, поэтому тайм-ауты могут накапливаться. - person Jonathon Faust; 16.03.2010
comment
Я печатал аналогичный ответ, поэтому +1. Другим вариантом было бы покончить с циклом for и каждый вызов distributedDrawPolys() устанавливал бы таймер для следующего с интервалом в 10 мс или около того. - person Andy E; 16.03.2010
comment
Привет, TJ, я просто хотел сказать, что ценю количество усилий, которые вы приложили к этому ответу, и, как только я понял, что я запутался в setTimeout в первую очередь (частично из-за комментария Энди Э.), это обрело больше смысла . Тем не менее, я бы не добился этого без примера джиттера. - 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: Хороший улов, я не заглядывал внутрь этой функции. В любом случае, это не должно быть решением. Это просто шаблон для длинных неблокирующих обновлений пользовательского интерфейса. - person Chetan S; 16.03.2010