Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.4k views
in Technique[技术] by (71.8m points)

javascript - Using setTimeout to update progress bar when looping over multiple variables

Suppose you have 3 arrays you want to loop over, with lengths x, y, and z, and for each loop, you want to update a progress bar. For example:

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

However, in this example, the progress bar doesn't update until the function completes. Therefore, I believe I need to use setTimeout to make the progress bar update while the function runs, although I'm not sure how to do that when you have nested for loops.

Do I need to break each loop up into its own function, or can I leave them as nested for loops?

I created a jsfiddle page in case you'd like to run the current function: http://jsfiddle.net/jrenfree/6V4Xp/

Thanks!

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

TL;DR: Use CPS: http://jsfiddle.net/christophercurrie/DHqeR/

The problem with the code in the accepted answer (as of Jun 26 '12) is that it creates a queue of timeout events that don't fire until the triple loop has already exited. You're not actually seeing the progress bar update in real-time, but seeing a late report of what the values of the variables were at the time they were captured in the inner closure.

I'd expect that your 'recursive' solution looks a bit like using continuation-passing style to ensure that your loop doesn't continue until after you've yielded control via setTimeout. You might not know you were using CPS, but if you're using setTimeout to implement a loop, you're probably pretty close to it.

I've spelled out this approach for future reference, because it's useful to know, and the resulting demo performs better than the ones presented. With triple nested loops it looks a bit convoluted, so it may be overkill for your use case, but can be useful in other applications.

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

You can see on jsFiddle that this version updates quite smoothly.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...