pondělí 8. srpna 2011

Jak zpracovat velké objekty v JavaScriptu...

... aniž by se zasekl prohlížeč.
V práci jsem řešil jeden velký problém. Potřeboval jsem zpracovat mnoho JavaScriptových objektů a ne zrovna malých. Doufám, že nebude vadit, když se podělím s malým know how, které vykonávám pro firmu :-) Ale třeba si to přečte někdo, kdo mi poradí jiný a lepší způsob.
Hlavní problém je v tom, že uživatel vykoná pro něj obyčejnou akci a já (JavaScript) musím na pozadí stránky provést složité zpracování. V tuto chvíli, ač se to tak nezdá, je potřeba hrát na rychlost skriptů, které se provádí pouze na straně prohlížeče.
Např. tedy, uživatel klikne na označit vše v ul>li seznamu A, který má třeba 10 elementů a v seznamu B s 5000 elementy označit ty elementy, které obsahují v atributu rel hodnotu z označeného elementu v seznamu A.
Používám pro svojí práci knihovnu jQuery a musím říct, že funkcí each cesta fakt nevede. Každá tisícina vteřiny je pak znát a to hlavně, pokud necháte vše zpracovat jak se říká "na jedno našlápnutí" :-)
On je taky rozdíl v použitém prohlížeči, protože třeba Firefox si při spuštění takových skriptů je schopný vzít i dalších 300 MB paměti a 30% navíc na procesoru.
Další nepříjemná věc je ta, že takové skripty děláme hlavně pro klienty, kterým moc nevysvětlíme, že je to normální, když kliknou na jeden "odkaz" a najednou se jim začne zahřívat procesor jak cip a k tomu jim v prohlížeči vyskočí dotaz zda počkat na zpracování skriptu nebo skript ukončit a ještě k tomu se jim zobrazí, že prohlížeč neodpovídá :-D
No takže jediný řešení, který mě napadlo je zpracovat vždy kousek a poté zpracovat další kousek. Snažil jsem se něco vygooglit, ale nenašel jsem nikoho, kdo by toto řešil.
Výsledek je několik funkcí v JS a jQuery jako takovém, ale hlavně s tím, že vždy jsem dopředu věděl jak budou zpracovávané objekty velké a kolik toho bude.
Například asi takto:

(function(){
$.selectLinks = {
  all: function(){
    this.nextObject(0); // spustíme první element seznamu
  },
  nextObject: function(objectIndex){
    // ukončíme, protože neexistuje žádný element s tímto "indexem"
    if($("ul.list li[class!='active']:eq("+objectIndex+")").length == 0){
      return false;
    }

    var thisObj = $("ul.list li[class!='active']:eq("+objectIndex+")");

    // nějaké další akce v JS

    if(objectIndex % 100 == 0){ // modulo na každý stý záznam a následné zajištění pozastavení zpracovávání v prohlížeči na půl vteřiny
      window.setTimeout(function(){
        $.selectLinks.nextObject((objectIndex+1));
      }, 500);
    }else{
      $.selectLinks.nextObject((objectIndex+1));
    }
  }
};
})(jQuery);

Neříkám, že skripty se zrychlí nebo budou méně náročné na počítač, ale důležité v tomto případě je hlavně nedát najevo klientům, že vznikla nějaká chyba s jejich draze zaplaceným systémem.
Výše uvedený kód jsem tu vyhaluzil jen tak na první našlápnutí, takže ani nemusí v podstatě fungovat, nicméně důležité na tom vidět je.
Hlavní a myslím si, že nejjednodušší je pouštět jednu funkci rekurzivně dokud je to možné s tím, že občas počkáme pomocí setTimeout na nové spuštění, k tomu klidně stačí půl vteřiny i méně.
V mém případě na projektu v práci jde o daleko složitější skripty, které zahrnují spoustu jiných věcí.
Budu rád, když někdo hodí koment a třeba mě i přiučí lepším věcem.

Ave já

PS: Jo a kdyby někdo nevěděl, spouštěl bych to $.selectLinks.all();

Žádné komentáře:

Okomentovat

 
]]>