Comment éviter de geler le navigateur lorsque vous effectuez des calculs de longue durée en Javascript


J'ai une page Web où un calcul javascript dans une fonction prend beaucoup de temps à se terminer et fait geler la page. Quelle technique dois-je utiliser pour m'assurer que le javascript ne gèle pas le navigateur lorsque le calcul se produit en arrière-plan?

Author: Ravi, 2012-11-25

5 answers

Si vous avez seulement besoin de faire un calcul et que vous n'avez pas besoin d'accéder au DOM pendant le calcul de longue durée, vous avez deux options:

  1. Vous pouvez diviser le calcul en morceaux et faire une pièce à la fois sur un setTimeout(). À chaque appel setTimeout(), le navigateur sera libre de servir d'autres événements et maintiendra la page vivante et réactive. Lorsque vous avez terminé le dernier morceau du calcul, vous pouvez ensuite effectuer le résultat.
  2. , Vous pouvez exécuter le calcul en arrière-plan utilisation d'un webworker dans les navigateurs modernes. Lorsque le calcul est effectué dans le webworker, il renvoie un message au thread principal et vous pouvez ensuite mettre à jour le DOM avec le résultat.

Voici une réponse connexe qui montre également un exemple: Meilleure façon d'itérer sur un tableau sans bloquer l'interface utilisateur

 21
Author: jfriend00, 2017-05-23 12:25:30

Certains navigateurs n'ont qu'un seul thread pour exécuter votre code et mettre à jour l'interface utilisateur (en d'autres termes, jusqu'à ce que le calcul soit terminé, le navigateur apparaîtra "gelé"). Vous voudrez essayer d'effectuer l'action de manière asynchrone, d'une manière ou d'une autre.

Si le calcul est vraiment cher, vous voudrez peut-être faire un appel au serveur et laisser le serveur faire le calcul, et rappeler le client lorsque le calcul est terminé.

Si le calcul est de type de cher, vous pouvez essayer de le faire en morceaux sur le client. Ce n'est pas réellement asynchrone (car le client bloquera lors de l'exécution de chaque bloc) mais le but est de rendre les blocs suffisamment petits pour que le blocage ne soit pas perceptible.

 3
Author: Jon Newmuis, 2012-11-24 22:57:55

Permettez-moi de développer la réponse de @jfriend00 en donnant un exemple concret et dépouillé. Voici un processus JavaScript de longue durée qui peut être démarré en cliquant sur un bouton. Une fois qu'il s'exécute, il gèle le navigateur. Le processus consiste en une longue boucle qui répète une certaine charge de travail où une itération prend relativement peu de temps.

En raison du gel du navigateur, déboguer un script comme celui-ci n'est pas facile. Une alternative pour éviter le gel du navigateur est d'utiliser un travailleur Web. L'inconvénient de cette l'approche est la mauvaise débogabilité des travailleurs Web en soi: des outils comme Firebug ne sont pas pris en charge.

<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>

En utilisant une structure de données de file d'attente (par exemple http://code.stephenmorley.org/javascript/queues / ), une minuterie d'intervalle et une petite modification du flux de contrôle du processus d'origine, on peut construire une interface graphique qui ne gèle pas le navigateur, laisse le processus entièrement débogable et autorise même des fonctionnalités supplémentaires comme marcher, s'arrêter et s'arrêter.

Voici comment il va:

<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>

Bien que cette approche ne corresponde certainement pas à tous les scripts de longue durée auxquels on peut penser, elle est certainement peut être adapté à n'importe quel scénario de type boucle. Je l'utilise pour porter Numenta's HTM/CLA artificiel algorithmes d'intelligence au navigateur.

 2
Author: chessweb, 2015-06-18 21:05:40
 setTimeout(function() { ..code  }, 0);

Je recommande cela pour le temps d'exécution lourd, et aussi pour le chargement ajax, vous pouvez essayer d'ajouter

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

Si c'est dans le processus de chargement.

 0
Author: zero8, 2017-09-19 08:05:14

Je pense que cela devrait résoudre votre problème,

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

/ / Contenu Html complet

<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
Author: George Livingston, 2018-01-16 10:17:20