2009-04-03 5 views
53

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

$(xmlDoc).find("Object").each(function() { 
    //Processing here 
}); 
+4

Obtenez un langage plus rapide! Non, vraiment: à moins que ce ne soit absolument nécessaire, n'utilisez pas JS pour cela - comme vous le voyez, c'est 1) single-threaded et 2) slow. – Piskvor

+5

Ceci est une fonction côté client, et JS est nécessaire. –

+11

@Triptych - Et ses options sont? Certes, on pourrait espérer que beaucoup de travail de ce genre pourrait être effectué côté serveur, mais comme nous ne connaissons pas sa situation, il est préférable de supposer qu'il a de bonnes raisons de le faire côté client et de travailler côté client dans une application web , vous avez seulement vraiment le choix entre Javascript et, bien ... Javascript. – Toji

Répondre

67

Je Ditch la fonction « chacun » en faveur d'une boucle car il est plus rapide. Je voudrais également ajouter quelques attentes en utilisant le "setTimeout" mais seulement de temps en temps et seulement si nécessaire. Vous ne voulez pas attendre 5ms à chaque fois car le traitement de 3500 enregistrements prend environ 17,5 secondes. Ci-dessous un exemple d'utilisation d'une boucle for qui traite 100 enregistrements (vous pouvez ajuster cela) à intervalles de 5 ms, ce qui donne un temps système 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 référence les différentes parties du traitement XML pour voir s'il y a un goulet d'étranglement quelque part qui peut être fixé. Vous pouvez référence dans Firefox en utilisant le profileur de Firebug et en écrivant à la console comme ceci:

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

Hope this helps.

+5

C'était une bonne idée - utilisez setTimeout périodiquement. Cela fonctionne avec un timeout de 0. –

+0

J'ai fait exactement cela pour plusieurs applications web qui nécessitaient un traitement de données massif du côté client. Fonctionne comme un charme, même si cela nécessite un peu de restructuration. – Toji

+8

Code cool. Peut-être qu'il me manque quelque chose, mais j'ai dû ajouter un 'index ++ 'et un' break' après le setTimeout() pour que cela fonctionne. –

22

Définissez un timeOut entre le traitement pour empêcher le cycle de boucle de ronger toutes les ressources du navigateur. Au total, il ne faudrait que quelques secondes pour traiter et boucler tout, ce qui n'est pas déraisonnable pour 3500 éléments.

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

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

    //process element; 

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

processing(); 
+1

devrait même fonctionner avec un délai de 0 – Christoph

+0

J'ai décidé de cette méthode, sauf que je ne lance que le setTimeout tous les 50 éléments. Et oui, cela fonctionne avec un délai de 0. –

+0

merci beaucoup pour ce code –

2

Javascript est mono-thread, donc en dehors de setTimeout, il n'y a pas grand-chose que vous pouvez faire. Si l'utilisation de Google Gears est une option pour votre site, ils offrent la possibilité de lancer javascript dans un véritable fil de discussion.

6

Je considérerais convertir les éléments de 3500 du xml au serveur de JSON ou même mieux le télécharger au serveur converti, de sorte qu'il soit natif à JS du getgo.

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

1

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

+0

Je suis d'accord avec Workers API. – FidEliO

+5

Mais vous ne pouvez pas faire de manipulation DOM à partir de l'API Worker –

2

vous pouvez setTimeout() avec une durée de zéro et elle donnera à volonté

3

De longues boucles sans geler le navigateur sont possibles avec le framework Turboid. Avec elle, 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: Real loops in Javascript

1

J'ai eu le même problème qui se passait lorsque l'utilisateur rafraîchi la page successivement. La raison était deux imbriqués pour des boucles qui se sont produites plus de 52000 fois. Ce problème était plus grave dans Firefox 24 que dans Chrome 29, car Firefox planterait plus tôt (environ 2000 ms plus tôt que Chrome). Ce que j'ai simplement fait et cela a fonctionné était que j'utilisais des boucles "for" au lieu de chacune et ensuite j'ai refactorisé le code de sorte que je divise le tableau entier en 4 appels séparés puis fusionné le résultat en un. Cette solution a prouvé qu'elle a fonctionné.

Quelque chose comme ceci:

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 travaillé pour moi était la même solution mise en œuvre avec Worker APIs de HTML5. Utilisez le même concept chez les travailleurs car ils évitent que votre navigateur soit gelé car ils s'exécutent en arrière-plan de votre thread principal. Si cela ne fonctionnait pas avec l'API Workers, placez chaque instance de loopForSubset dans différents opérateurs 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 aider avec plus de morceaux de code réel, si quelqu'un pense encore que cela pourrait les enchaîner.

1

Vous pouvez essayer de raccourcir le code par

$(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 beaucoup de mal;)

0

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

//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(); 
Questions connexes