How to avoid freezing the browser when doing long-running computations in Javascript

后端 未结 5 1918
慢半拍i
慢半拍i 2020-11-28 09:15

I have a web page where a javascript calculation in a function takes lot of time to finish and makes the page to freeze. What technique should I use to make sure the javascr

相关标签:
5条回答
  • 2020-11-28 09:58

    Some browsers have only one thread for running your code and updating the UI (in other words, until the calculation is complete, the browser will appear "frozen"). You'll want to try to perform the action asynchronously, in one way or another.

    If the calculation is really expensive, you might want to make a call to the server and let the server do the calculation, and callback the client when the calculation is done.

    If the calculation is kind of expensive, you can try to do it in chunks on the client. This isn't actually asynchronous (as the client will block while executing each chunk) but the goal is to make the chunks small enough that the blocking is not noticeable.

    0 讨论(0)
  • 2020-11-28 09:58

    I think this should resolve your problem,

    function myClickOperation(){
        var btn_savebutton2 = document.querySelector("input[id*='savebutton2']");
        setTimeout(function () { btn_savebutton2.click() }, 1000);
    }
    

    // Full Html content

    <html>
    <script>
        function myClickOperation(){
            var btn_savebutton2 = document.querySelector("input[id*='savebutton2']");
            document.getElementById('savebutton1').disabled = true;
            setTimeout(function () { btn_savebutton2.click() }, 1000);
        }
        function testClick(){
            var idd = document.getElementById("myid");
            idd.innerHTML =idd.innerHTML +"<br/>" + new Date();
            if(true){
                setTimeout(function () { testClick() }, 1);
            }
        }
    
    </script>
    <body>
        <input type="button" id="savebutton1" onclick="myClickOperation()" value="Click me" />
        <input type="button" id="savebutton2" onclick="testClick()" value="Do not click this" />
        <input type="text"/>
    
        <input type="button" value="temp"/>
        <div style="height: 300px;overflow-y: scroll;" id="myid"/>
    </body>
    

    0 讨论(0)
  • 2020-11-28 10:00

    Let me elaborate on @jfriend00's answer by giving a concrete stripped down example. Here is a long-running JavaScript process that can be started by clicking a button. Once it runs, it freezes the browser. The process consists of a long loop that repeats some workload where one iteration takes comparatively little time.

    Due to the browser freeze, debugging a script like this is not easy. One alternative to avoid browser freeze is using a web worker. The drawback of that approach is the poor debuggabilty of web workers per se: Tools like Firebug are not supported.

    <html>
    <head>
        <script>
            var Process = function(start) {
                this.start = start;
            }
    
            Process.prototype.run = function(stop) {
                // Long-running loop
                for (var i = this.start; i < stop; i++) {
                    // Inside the loop there is some workload which 
                    // is the code that is to be debugged
                    console.log(i);
                }
            }
    
            var p = new Process(100);
    
            window.onload = function() {
                document.getElementById("start").onclick = function() {
                    p.run(1000000000);
                }
            }
        </script>
    </head>
    <body>
        <input id="start" type="button" value="Start" />
    </body>
    </html>
    

    Using a Queue data structure (e.g. http://code.stephenmorley.org/javascript/queues/), an interval timer and some small modification to the control flow of the original process one can build a GUI that doesn't freeze the browser, leaves the process fully debuggable and even allows additional features like stepping, pausing and stopping.

    Here is how it goes:

    <html>
    <head>
        <script src="http://code.stephenmorley.org/javascript/queues/Queue.js"></script>
        <script>
            // The GUI controlling process execution
            var Gui = function(start) {
                this.timer = null; // timer to check for inputs and/or commands for the process
                this.carryOn = false; // used to start/pause/stop process execution
                this.cmdQueue = new Queue(); // data structure that holds the commands 
                this.p = null; // process instance
                this.start = start;
                this.i = start; // input to the modified process 
            }
    
            Gui.prototype = {
                /**
                 * Receives a command and initiates the corresponding action 
                 */
                executeCmd: function(cmd) {
                    switch (cmd.action) {
                        case "initialize":
                            this.p = new Process(this);
                            break;
                        case "process":
                            this.p.run(cmd.i);
                            break;
                    }
                },
    
                /*
                 * Places next command into the command queue
                 */
                nextInput: function() {
                    this.cmdQueue.enqueue({
                        action: "process",
                        i: this.i++
                    });
                }
            }
    
            // The modified loop-like process
            var Process = function(gui) {
                this.gui = gui;
            }
    
            Process.prototype.run = function(i) {
                // The workload from the original process above
                console.log(i);
    
                // The loop itself is controlled by the GUI
                if (this.gui.carryOn) {
                    this.gui.nextInput();
                }
            }
    
            // Event handlers for GUI interaction
            window.onload = function() {
    
                var gui = new Gui(100);
    
                document.getElementById("init").onclick = function() {
                    gui.cmdQueue.enqueue({ // first command will instantiate the process
                        action: "initialize"
                    });
    
                    // Periodically check the command queue for commands
                    gui.timer = setInterval(function() {
                        if (gui.cmdQueue.peek() !== undefined) {
                            gui.executeCmd(gui.cmdQueue.dequeue());
                        }
                    }, 4);
                }
    
                document.getElementById("step").onclick = function() {
                    gui.carryOn = false; // execute just one step
                    gui.nextInput();
                }
    
                document.getElementById("run").onclick = function() {
                    gui.carryOn = true; // (restart) and execute until further notice
                    gui.nextInput();
                }
    
                document.getElementById("pause").onclick = function() {
                    gui.carryOn = false; // pause execution
                }
    
                document.getElementById("stop").onclick = function() {
                    gui.carryOn = false; // stop execution and clean up 
                    gui.i = gui.start;
                    clearInterval(gui.timer)
    
                    while (gui.cmdQueue.peek()) {
                        gui.cmdQueue.dequeue();
                    }
                }
            }
        </script>
    </head>
    <body>
        <input id="init" type="button" value="Init" />
        <input id="step" type="button" value="Step" />
        <input id="run" type="button" value="Run" />
        <input id="pause" type="button" value="Pause" />
        <input id="stop" type="button" value="Stop" />
    </body>
    </html>
    

    While this approach certainly doesn't fit all long-running scripts one can think of, it certainly can be adapted to any loop-like scenario. I'm using it to port Numenta's HTM/CLA artificial intelligence algorithms to the browser.

    0 讨论(0)
  • 2020-11-28 10:02

    If you only need to do a calculation and don't need to access the DOM during the long running calculation, then you have two options:

    1. You can break the calculation up into pieces and do a piece at a time on a setTimeout(). On each setTimeout() call, the browser will be free to serve other events and will keep the page alive and responive. When you finish the last piece of the calculation, you can then carry out the result.
    2. You can run the calculation in the background using a webworker in modern browsers. When the calcuation is done in the webworker, it sends a message back to the main thread and you can then update the DOM with the result.

    Here's a related answer that also shows an example: Best way to iterate over an array without blocking the UI

    0 讨论(0)
  • 2020-11-28 10:04
     setTimeout(function() { ..code  }, 0);
    

    I recommend this for heavy execution time, and also for on load ajax you could try to add

    $(window).on("load", function (e) { }); // for jquery v3
    

    if its in the loading process.

    0 讨论(0)
提交回复
热议问题