web-dev-qa-db-fra.com

Comment empêcher la boucle Javascript intense de geler le navigateur

J'utilise Javascript pour analyser un fichier XML avec environ 3 500 éléments. J'utilise une fonction "each" jQuery, mais je pourrais utiliser n'importe quelle forme de boucle.
Le problème est que le navigateur se fige pendant quelques secondes pendant l'exécution de la boucle. Quel est le meilleur moyen d'arrêter de geler le navigateur sans ralentir trop le code?

$(xmlDoc).find("Object").each(function() {
    //Processing here
});
55
Chris B

Je laisserais tomber la fonction "each" en faveur d'une boucle for car elle est plus rapide. Je voudrais également ajouter quelques attentes en utilisant le "setTimeout", mais seulement de temps en temps et uniquement si nécessaire. Vous ne voulez pas attendre 5 ms à chaque fois car le traitement de 3 500 enregistrements prendrait environ 17,5 secondes.

Vous trouverez ci-dessous un exemple d'utilisation d'une boucle for qui traite 100 enregistrements (vous pouvez ajuster cela) à des intervalles de 5 ms, ce qui donne un temps système supplémentaire de 175 ms.

var xmlElements = $(xmlDoc).find('Object');
var length = xmlElements.length;
var index = 0;
var process = function() {
  for (; index < length; index++) {
    var toProcess = xmlElements[index];
    // Perform xml processing
    if (index + 1 < length && index % 100 == 0) {
        setTimeout(process, 5);
    }
  }
};
process();

Je voudrais également analyser les différentes parties du traitement XML pour voir s'il existe un goulot d'étranglement à un endroit qui peut être corrigé. Vous pouvez évaluer les performances de Firefox avec le profileur de firebug et en écrivant sur la console de la manière suivante:

// start benchmark
var t = new Date();
// some xml processing
console.log("Time to process: " + new Date() - t + "ms");

J'espère que cela t'aides.

68
Helgi

Définissez un délai d'attente entre les traitements pour empêcher le cycle de boucle de consommer toutes les ressources du navigateur. Au total, cela ne prend que quelques secondes pour tout traiter et tout parcourir, ce qui n'est pas déraisonnable pour 3 500 éléments.

var xmlElements = $(xmlDoc).find('Object');

var processing = function() {
  var element = xmlElements.shift();

  //process element;

  if (xmlElements.length > 0) {
    setTimeout(processing, 5);
  }
}

processing();
22
tj111

J'envisagerais de convertir les éléments 3500 de xml en serveurs JSON ou, encore mieux, de les télécharger sur un serveur converti, afin qu'il soit natif de JS à partir de getgo.

Cela réduirait votre charge et réduirait la taille du fichier.

6

Les longues boucles sans geler le navigateur sont possibles avec le framework Turboid. Avec cela, vous pouvez écrire du code comme:

loop(function(){  
        // Do something...  
}, number_of_iterations, number_of_milliseconds);

Plus de détails dans cet article turboid.net: Les boucles réelles en Javascript

3
sharp solution

vous pouvez setTimeout () avec une durée de ZERO et céder le résultat

2
Scott Evernden

Javascript est à thread unique, donc à part setTimeout, vous ne pouvez pas faire grand chose. Si Google Gears est une option pour votre site, ils permettent d’exécuter javascript dans un véritable fil d’arrière-plan.

2
Gabe Moothart

Vous pouvez utiliser l'API HTML5 workers, mais cela ne fonctionnera que sur Firefox 3.1 et Safari 4 betas atm.

1
olliej

Vous pouvez essayer de raccourcir le code en

   $(xmlDoc).find("Object").each(function(arg1) {
    (function(arg1_received) {
                setTimeout(function(arg1_received_reached) {

                    //your stuff with the arg1_received_reached goes here 

                }(arg1_received), 0)
            })(arg1)
}(this));

Cela ne vous fera pas beaucoup de mal;)

1
LINTUism

J'ai eu le même problème qui se produisait lorsque l'utilisateur a rafraîchi la page successivement. La raison était deux boucles imbriquées qui se sont produites plus de 52 000 fois. Ce problème était plus grave dans Firefox 24 que dans Chrome 29, car Firefox se planterait plus tôt (environ 2000 ms plus tôt que Chrome). Ce que j'ai simplement fait et que cela a fonctionné, c’est que j’utilisais les boucles "pour" au lieu de chacune d’elles, puis je refactaisais le code pour diviser tout le tableau de boucles en 4 appels séparés, puis fusionner le résultat en un seul. Cette solution a prouvé que cela a fonctionné.

Quelque chose comme ça:

var entittiesToLoop = ["..."]; // Mainly a big array
   loopForSubset(0, firstInterval);
   loopForSubset(firstInterval, secondInterval);
    ...

var loopForSubset = function (startIndex, endIndex) {
    for (var i=startIndex; i < endIndex; i++) {
            //Do your stuff as usual here
    }
}

L'autre solution qui a également fonctionné pour moi est la même solution mise en œuvre avec Worker APIs à partir de HTML5. Utilisez le même concept chez les travailleurs car ils évitent que votre navigateur ne soit gelé, car ils fonctionnent en arrière-plan de votre fil principal. Si simplement appliquer cela avec l'API Workers ne fonctionnait pas, placez chacune des instances de loopForSubset dans différents travailleurs et fusionnez le résultat dans l'appelant principal de Worker.

Je veux dire que cela pourrait ne pas être parfait, mais cela a fonctionné. Je peux vous aider avec davantage de morceaux de code réels, si quelqu'un pense toujours que cela pourrait leur convenir.

1
FidEliO

En tant que modification de @ tj111, répondez au code utilisable complet 

    //add pop and shift functions to jQuery library. put in somewhere in your code.
    //pop function is now used here but you can use it in other parts of your code.
    (function( $ ) {
        $.fn.pop = function() {
            var top = this.get(-1);
            this.splice(this.length-1,1);
            return top;
        };

        $.fn.shift = function() {
            var bottom = this.get(0);
            this.splice(0,1);
            return bottom;
        };
    })( jQuery );


//the core of the code:
    var $div = $('body').find('div');//.each();
    var s= $div.length;
    var mIndex = 0;
    var process = function() {
        var $div = $div.first();            
    //here your own code.

    //progress bar:
        mIndex++;
    // e.g.:    progressBar(mIndex/s*100.,$pb0);

    //start new iteration.
        $div.shift();
        if($div.size()>0){
            setTimeout(process, 5);
        } else {
    //when calculations are finished.
            console.log('finished');
        }
    }
    process();
0
Vyacheslav