2009-04-03 4 views
53

Ich verwende Javascript, um eine XML-Datei mit etwa 3.500 Elementen zu analysieren. Ich benutze eine jQuery "each" -Funktion, aber ich könnte jede Art von Schleife verwenden.
Das Problem ist, dass der Browser für einige Sekunden einfriert, während die Schleife ausgeführt wird. Was ist der beste Weg, den Browser nicht mehr zu blockieren, ohne den Code zu sehr zu verlangsamen?So stoppen Sie die Javascript-Schleife vom Einfrieren des Browsers

+4

Erhalten Sie beantworten ein schnellere Sprache! Nein, wirklich: Wenn es nicht absolut notwendig ist, verwenden Sie JS dafür nicht. Wie Sie sehen, ist es 1) single-threaded und 2) langsam. – Piskvor

+5

Dies ist eine clientseitige Funktion, und JS ist erforderlich. –

+11

@Triptych - Und seine Optionen sind? Sicherlich würde man hoffen, dass viel schweres Heben wie dieses serverseitig durchgeführt werden könnte, aber da wir seine Situation nicht kennen, ist es am besten davon auszugehen, dass er einen guten Grund dafür hat, es clientseitig zu machen und in einer Web-App zu arbeiten Du hast wirklich nur die Wahl zwischen Javascript und, naja ... Javascript. – Toji

Antwort

67

gewünscht ergeben würde ich die „jeder“ -Funktion für eine for-Schleife Graben, da es schneller ist. Ich würde auch einige Wartezeiten mit dem "setTimeout" hinzufügen, aber nur so oft und nur bei Bedarf. Sie möchten nicht jedes Mal 5 ms warten, da die Verarbeitung von 3500 Datensätzen ca. 17,5 Sekunden dauert.

Unten ist ein Beispiel mit einer for-Schleife, die 100 Datensätze verarbeitet (Sie können dies optimieren) in 5 ms Intervallen, die einen 175 ms Overhead ergibt.

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

ich Benchmark auch würden die verschiedenen Teile der XML-Verarbeitung, um zu sehen, ob es einen Engpass irgendwo ist, die behoben werden kann. Sie können Benchmark in Firefox mit Firebug-Profiler und wie diese auf der Konsole auszuschreiben:

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

Hoffnung, das hilft.

+5

Dies war eine großartige Idee - verwenden Sie die setTimeout regelmäßig. Es funktioniert mit einer Zeitüberschreitung von 0. –

+0

Ich habe genau das für mehrere Web-Anwendungen getan, die massive Datenverarbeitung auf dem Client-Ende erfordert. Funktioniert wie ein Zauber, auch wenn es ein wenig Umstrukturierung erfordert. – Toji

+8

Cooler Code. Vielleicht fehlt mir etwas, aber ich musste nach dem setTimeout() einen 'index ++' und einen 'break' hinzufügen, um dies zum Laufen zu bringen. –

22

Legen Sie einen timeOut zwischen der Verarbeitung fest, um zu verhindern, dass der Schleifenzyklus alle Browser-Ressourcen verbraucht. Insgesamt würde es nur ein paar Sekunden dauern, alles zu bearbeiten und zu durchlaufen, was für 3.500 Elemente nicht unangemessen ist.

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

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

    //process element; 

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

processing(); 
+1

sollte sogar mit einem Timeout von 0 – Christoph

+0

Ich entschied mich für diese Methode, außer dass ich nur die setTimeout alle 50 Elemente ausführen. Und ja, es funktioniert mit einem Timeout von 0. –

+0

Vielen Dank für diesen Code –

2

Javascript ist single-threaded, so abgesehen von setTimeout, gibt es nicht viel Sie tun können. Wenn Google Gears eine Option für Ihre Website ist, können Sie Javascript in einem echten Hintergrundthread ausführen.

6

Ich würde in Betracht ziehen, die 3500 Elemente von XML in JSON serverseitig zu konvertieren oder es sogar auf den konvertierten Server hochzuladen, so dass es in JS vom getgo stammt.

Dies würde Ihre Last minimieren und prolly die Dateigröße auch kleiner machen.

1

Sie könnten die HTML5-Worker-API verwenden, aber das funktioniert nur unter Firefox 3.1 und Safari 4 betas atm.

+0

Ich stimme mit Workers API. – FidEliO

+5

Aber Sie können nicht DOM Manipulation von Worker API –

2

Sie können setTimeout() mit einer Dauer von ZERO und es wird als

3

Lange Schleifen ohne den Browser einzufrieren ist mit dem Turboid-Framework möglich. Mit ihm können Sie schreiben Code wie:

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

Mehr Details in diesem turboid.net Artikel: Real loops in Javascript

1

ich hatte das gleiche Problem, das passiert, wenn der Benutzer nacheinander die Seite aktualisiert. Der Grund dafür waren zwei verschachtelte For-Schleifen, die mehr als 52000 mal aufgetreten sind. Dieses Problem war in Firefox 24 härter als in Chrome 29, da Firefox früher abstürzen würde (etwa 2000 ms früher als Chrome). Was ich einfach getan habe und es funktionierte war, dass ich "for" -Schleifen anstelle von jedem benutze und dann den Code so umstrukturierte, dass ich das ganze Schleifenarray auf 4 getrennte Aufrufe aufteilte und dann das Ergebnis in eins zusammenfasste. Diese Lösung hat bewiesen, dass es funktioniert hat.

Etwas wie folgt aus:

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

Die andere Lösung, die auch für mich gearbeitet wurde die gleiche Lösung mit Worker APIs von HTML5 umgesetzt. Verwenden Sie das gleiche Konzept bei Arbeitern, da sie verhindern, dass Ihr Browser eingefroren wird, da sie im Hintergrund Ihres Hauptthreads ausgeführt werden. Wenn Sie das nur mit der Workers-API anwenden, können Sie die Instanz loopForSubset nicht in verschiedenen Arbeitern verwenden und das Ergebnis im Hauptaufruf von Worker zusammenführen.

Ich meine, das ist vielleicht nicht perfekt, aber das hat funktioniert. Ich kann mit mehr echten Code-Chunks helfen, wenn jemand immer noch denkt, das könnte ihnen entsprechen.

1

Sie könnten versuchen, den Code zu verkürzen durch

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

Dies wird nicht viel schaden;)

0

Als Modifikation von @ tj111 den vollen verwendbaren Code

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