Использование setTimeout для обновления индикатора выполнения при цикле по нескольким переменным - PullRequest
2 голосов
/ 07 октября 2011

Предположим, у вас есть 3 массива, которые вы хотите зациклить, с длинами x, y и z, и для каждого цикла вы хотите обновить индикатор выполнения.Например:

function run() {
    x = 100;
    y = 100;
    z = 10;
    count = 0;
    for (i=0; i<x; i++) {
        //some code
        for (j=0; j<y; j++) {
            // some code
            for (k=0; k<z; k++) {
                //some code
                $("#progressbar").reportprogress(100*++count/(x*y*z));
            }
        }
    }
}

Однако в этом примере индикатор выполнения не обновляется до завершения функции.Поэтому я считаю, что мне нужно использовать setTimeout, чтобы обновлять индикатор выполнения во время работы функции, хотя я не уверен, как это сделать, когда вы вложили в циклы.

Нужно ли прерывать каждый циклв свою собственную функцию, или я могу оставить их вложенными для циклов?

Я создал страницу jsfiddle на случай, если вы хотите запустить текущую функцию: http://jsfiddle.net/jrenfree/6V4Xp/

Спасибо!

Ответы [ 4 ]

4 голосов
/ 26 июня 2012

TL; DR: использовать CPS: http://jsfiddle.net/christophercurrie/DHqeR/

Проблема с кодом в принятом ответе (по состоянию на 26 июня '12 г.) заключается в том, что он создает очередь событий тайм-аута, которые не срабатывают до тех пор, пока тройной цикл уже не завершится. Вы на самом деле не видите обновления индикатора выполнения в режиме реального времени, но видите поздний отчет о том, какими были значения переменных во время их захвата во внутреннем закрытии.

Я бы ожидал, что ваше «рекурсивное» решение похоже на использование стиля передачи продолжения , чтобы гарантировать, что ваш цикл не продолжится до тех пор, пока вы не передадите управление через setTimeout. Возможно, вы не знаете, что используете CPS, но если вы используете setTimeout для реализации цикла, вы, вероятно, довольно близки к нему.

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

(function($){
    function run() {
        var x = 100,
            y = 100,
            z = 10,
            count = 0;

        /*
        This helper function implements a for loop using CPS. 'c' is
        the continuation that the loop runs after completion. Each
        'body' function must take a continuation parameter that it
        runs after doing its work; failure to run the continuation
        will prevent the loop from completing.
        */
        function foreach(init, max, body, c) {
            doLoop(init);
            function doLoop(i) {
                if (i < max) {
                    body(function(){doLoop(i+1);});
                }
                else {
                    c();
                }
            }
        }

        /*
        Note that each loop body has is own continuation parameter (named 'cx',
        'cy', and 'cz', for clarity). Each loop passes the continuation of the
        outer loop as the termination continuation for the inner loop.
        */
        foreach(0, x, function(cx) {
            foreach(0, y, function(cy) {
                foreach(0, z, function(cz) {
                    count += 1;
                    $('#progressbar').reportprogress((100*(count))/(x*y*z));
                    if (count * 100 % (x*y*z) === 0) {
                        /*
                        This is where the magic happens. It yields
                        control to the javascript event loop, which calls
                        the "next step of the foreach" continuation after
                        allowing UI updates. This is only done every 100
                        iterations because setTimeout can actually take a lot
                        longer than the specified 1 ms. Tune the iterations
                        for your specific use case.                   
                        */
                        setTimeout(cz, 1);
                    } else {
                        cz();
                    }
                }, cy);
            }, cx);
        }, function () {});    
    }

    $('#start').click(run);
})(jQuery);

Вы можете видеть на jsFiddle , что эта версия обновляется довольно гладко.

1 голос
/ 07 октября 2011

Вероятно, функция jquery в плагине reportprogress использует setTimeout. Например, если вы используете setTimeout и запускаете его через 0 миллисекунд, это не значит, что он будет запущен немедленно. Сценарий будет выполнен, когда не выполняется никакой другой JavaScript.

Здесь вы можете видеть, что я пытаюсь записать счетчик, когда он равен 0. Если я делаю это в функции обратного вызова setTimeout, то она выполняется после всех циклов, и вы получите 100000 без 0. Это объясняет, почему индикатор выполнения показывает только 100%. js Скриптовая ссылка на этот скрипт

function run() {
    x = 100;
    y = 100;
    z = 10;
    count = 0;
    for (i=0; i<x; i++) {
        //some code
        for (j=0; j<y; j++) {
            // some code
            for (k=0; k<z; k++) {
                //some code
                if(count===0) {
                     console.log('log emidiatelly ' + count);
                    setTimeout(function(){
                        console.log('log delayed ' + count);
                    },0);
                }
                count++;
            }
        }
    }
}
console.log('started');
run();
console.log('finished');

оборачивая все после for (i) в функцию обратного вызова setTimeout, заставила работать индикатор выполнения. js Fiddle link

Изменить: Только что проверил, что код установки стиля для элемента фактически выполняется все время. Я думаю, что это может быть приоритетом браузера, чтобы сначала выполнить javascript, а затем отобразить изменения CSS.

Я написал еще один пример, где я заменил первый цикл for функцией setInterval. Немного неправильно использовать это так, но, возможно, вы сможете решить эту проблему с помощью этого хака.

var i=0;
var interval_i = setInterval(function (){

    for (j=0; j<y; j++) {
        for (k=0; k<z; k++) {
            $("#progressbar").reportprogress(100*++count/(x*y*z));
        }
    }

  i++;
  if((i<x)===false) {
    clearInterval(interval_i);
  }
},0);

JS Fiddle

1 голос
/ 07 октября 2011

Если вы хотите использовать setTimeout, вы можете записать переменные x, y, z и count в замыкание:

function run() {
    var x = 100,
        y = 100,
        z = 10,
        count = 0;
    for (var i=0; i<x; i++) {
        for (var j=0; j<y; j++) {
            for (var k=0; k<z; k++) {
                (function(x, y, z, count) {
                    window.setTimeout(function() {
                        $('#progressbar').reportprogress((100*count)/(x*y*z));
                    }, 100);
                })(x, y, z, ++count);
            }
        }
    }
}

Живая демонстрация .

0 голосов
/ 10 февраля 2012

Я нашел решение, основанное на последнем ответе, но изменив интервал времени на единицу.Это решение показывает загрузчик, пока основной поток выполняет интенсивную задачу.

Определите эту функцию:

       loading = function( runme ) {
                    $('div.loader').show();
                    var interval = window.setInterval( function() { 
                          runme.call();  
                          $('div.loader').hide();
                          window.clearInterval(interval);
                    }, 1 );
               };

И назовите ее так:

       loading( function() {
                // This take long time...
                data.sortColsByLabel(!data.cols.sort.asc);
                data.paint(obj);
              });
...