2009-04-03 8 views
53

Sto usando Javascript per analizzare un file XML con circa 3.500 elementi. Sto usando una funzione "ogni" jQuery, ma potrei usare qualsiasi forma di loop.
Il problema è che il browser si blocca per alcuni secondi mentre il ciclo viene eseguito. Qual è il modo migliore per fermare il blocco del browser senza rallentare troppo il codice?Come interrompere intenso ciclo di Javascript dal blocco del browser

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

Ottenere un linguaggio più veloce! No, davvero: a meno che non sia assolutamente necessario, non usare JS per questo - come vedi, è 1) single-threaded e 2) slow. – Piskvor

+5

Questa è una funzione lato client e JS è necessario. –

+11

@Triptych - E le sue opzioni sono? Sicuramente si potrebbe sperare che un sollevamento molto pesante come questo possa essere eseguito lato server, ma dal momento che non conosciamo la sua situazione è meglio supporre che abbia una buona ragione per farlo lato client, e quando si lavora lato client in una web app , hai solo una scelta tra Javascript e, beh ... Javascript. – Toji

risposta

67

avrei fosso il "ogni" funzione a favore di un ciclo for in quanto è più veloce. Aggiungerei anche alcune attese usando "setTimeout" ma solo ogni tanto e solo se necessario. Non si desidera attendere 5 ms ogni volta, poiché l'elaborazione di 3500 record richiede circa 17,5 secondi.

Di seguito è riportato un esempio che utilizza un ciclo for che elabora 100 record (è possibile modificarlo) a intervalli di 5 ms che forniscono un overhead di 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(); 

avrei anche riferimento alle diverse parti del trattamento XML per verificare se v'è un collo di bottiglia da qualche parte che può essere fissato. È punto di riferimento in Firefox possono utilizzare profiler di Firebug e scrivendo fuori per la console in questo modo:

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

Spero che questo aiuti.

+5

Questa è stata una grande idea: utilizzare periodicamente setTimeout. Funziona con un timeout di 0. –

+0

Ho fatto esattamente questo per diverse applicazioni web che richiedevano un'enorme elaborazione dei dati sul client. Funziona come un fascino, anche se richiede un po 'di ristrutturazione. – Toji

+8

Cool code. Forse mi manca qualcosa, ma ho dovuto aggiungere un 'index ++' e un 'break' dopo il setTimeout() per farlo funzionare. –

22

Impostare un intervallo di tempo tra l'elaborazione per evitare che il ciclo del ciclo mangi tutte le risorse del browser. In totale, ci vorranno solo pochi secondi per elaborare e scorrere tutto, non irragionevole per 3.500 elementi.

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

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

    //process element; 

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

processing(); 
+1

dovrebbero funzionare anche con un timeout di 0 – Christoph

+0

Ho deciso su questo metodo, tranne che eseguo solo setTimeout ogni 50 elementi. E sì, funziona con un timeout di 0. –

+0

grazie mille per quel codice –

2

JavaScript è single-threaded, quindi a parte setTimeout, non c'è molto che si può fare. Se si utilizza Google Gears è un'opzione per il tuo sito, forniscono la possibilità di eseguire javascript in un vero thread in background.

6

Prenderei in considerazione la conversione dei 3500 elementi da xml a server JSON o ancora meglio caricarli sul server convertito, in modo che sia nativo di JS dal getgo.

Ciò ridurrebbe al minimo il carico e ridurrebbe ulteriormente le dimensioni del file.

1

È possibile utilizzare l'API di lavoro HTML5, ma funzionerà solo su Firefox 3.1 e Safari 4 beta.

+0

Sono d'accordo con Workers API. – FidEliO

+5

Ma non è possibile eseguire DOM Manipulation da Worker API –

2

si può setTimeout() con la durata di ZERO e sarà resa, se lo desideri

3

I loop lunghi senza il blocco del browser sono possibili con il framework Turboid. Con esso, è possibile scrivere codice simile:

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

Maggiori dettagli in questo articolo turboid.net: Real loops in Javascript

1

Ho avuto lo stesso problema che stava accadendo quando l'utente aggiorna la pagina di successione. Il motivo era due cicli nidificati che si ripetevano più di 52000 volte. Questo problema era più duro in Firefox 24 rispetto a Chrome 29 dato che Firefox si arrestava prima (circa 2000 ms prima di Chrome). Quello che ho semplicemente fatto e che ha funzionato è stato che ho usato "for" loop invece di ciascuno e poi ho rifattorizzato il codice in modo da dividere l'intero loop array in 4 chiamate separate e quindi unire il risultato in uno. Questa soluzione ha dimostrato che ha funzionato.

Qualcosa di simile a questo:

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'altra soluzione che ha funzionato anche per me era la stessa soluzione implementata con Worker APIs da HTML5. Utilizza lo stesso concetto nei lavoratori in quanto evita che il browser venga bloccato perché viene eseguito sullo sfondo del thread principale. Se solo applicando questa API Workers non ha funzionato, posiziona ciascuna istanza di loopForSubset in diversi worker e unisci il risultato all'interno del chiamante principale di Worker.

Voglio dire che questo potrebbe non essere perfetto ma questo ha funzionato. Posso aiutarti con blocchi di codice più reali, se qualcuno continua a pensare che questo li possa ospitare.

1

Si potrebbe provare a ridurre il codice da

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

Questo non ti farà del male molto;)

0

come modifica di @ tj111 rispondere il codice utilizzabile piena

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