Čekání na AJAX

Optimalizace AJAX requestů

Posíláte AJAX request a čekáte na jeho odpověď… a co takhle ten čas nějak využít? Třeba pro přípravu DOM prvků a JS proměnných.

Čekání na AJAX

Představte si běžnou situaci – uživatel klikne na tlačítko, zobrazí se Spinner, na pozadí se stáhne nový obsah a následně se zobrazí do stránky.

function loadContent(container, url) {
    $(container)
        .empty()
        .append(new Spinner())
    ;

    $.ajax({
        url: url,
        success: function(html) {
            $(container)
                .empty()
                .append($(html))
                .addClass('loaded')
            ;
        }
    });
}

Určitě si říkáte, jak se tahle zdánlivě složitá operace dá snadno a rychle zpracovat pomocí jQuery.

A teď si to zkusme rozebrat z pohledu prohlížeče:

  1. čekám na vytvoření jQuery wrapperu pro kontejner
  2. čekám na vyčištění kontejneru
  3. čekám na vytvoření spinneru
  4. čekám na odpověď od serveru
  5. čekám na odpověď od serveru
  6. čekám na odpověď od serveru (trvá to fakt dlohou, ne?)
  7. čekám na vytvoření jQuery wrapperu pro kontejner (znovu)
  8. čekám na vyčištění kontejneru (znovu)
  9. čekám na zpracování HTML kódu

Není to divné, že i když jsem to napsali tak rychle, prohlížeč pořád jen na něco čeká?

Jak si čekání zpříjemnit?

A teď si přestavte tu samou situaci, ale tak, že čekání na odpověď od serveru využijeme pro všechny ostatní potřebné akce a přípravy:

  1. zatímco čekám na odpověď od serveru, vytvořím si jQuery wrapper pro kontejner
  2. zatímco čekám na odpověď od serveru, vyčistím kontejner
  3. zatímco čekám na odpověď od serveru, vytvořím spinner
  4. čekám na odpověď od serveru (ještě chvilku a už to bude)
  5. čekám na zpracování HTML kódu

A jak by to vypadalo v kódu?

function loadContent(container, url) {
    var $container, $spinner;

    //nejprve pošleme request...
    $.ajax({
        url: url,
        success: function(html) {
            $spinner.replaceWith($(html));
            $container.addClass('loaded');
        }
    });

    //...a čekání využijeme pro ostatní akce
    $spinner = $(new Spinner());
    $container = $(container)
        .empty()
        .append($spinner)
    ;

} 

V čem se tento druhý kód tak liší? Hlavně tím, že před odesláním requestu na server neprovádí žádné zbytečné akce, které může provést až později. Na odeslání AJAX požadavku zpravidla nepotřebujete nic jiného než URL (a možná nějaké POST parametry).

Všechny ostatní akce, jako je změna DOM prvků a vytvoření JS objektů můžeme provést v čase, kdy čekáme na odpověď.

Přípravu kontejneru pro nový obsah tedy uděláme až po odeslání requestu. Většina prohlížečů stejně DOM překresluje až na konci JS funkce, takže z pohledu uživatele vyjde nastejno, zda Spinner zobrazíte na začátku nebo na konci funkce.

Navíc si vytvoření jQuery wrapper uložíme do proměnné, protože každé jeho vytvoření nějakou dobu trvá, takže se callback nebude muset zdržovat jeho novým vytvářením. Callback sice používá proměnnou v kódu výše, než kde do ní přiřazujeme hodnotu, ale díky systému uzávěr a zpracování funkcí v JS (nemá vlákna) k použití proměnné dojde vždy až po vytvoření a uložení wrapperu.

Uzávěra (anglicky Closure) vždy pracuje s aktuální hodnotou proměnné; nikoliv s hodnotou, která byla v proměnné v okamžiku jejího využití (uzavření) ve vnitřní funkci. Díky tomu můžete do proměnné přiřadit hodnotu i po té, co jste vytvořili vnořenou funkci, ale před tím, než ji zavoláte.

Pro další urychlení využijeme jQuery funkci replaceWith() tak, že si uložíme odkaz na Spinner, který je během načítání jediným obsahem kontejneru, a po stažení nového HTML jím Spinner nahradíme a ušetříme volání funkce empty().

A co známý problém s dvojitým odesláním, říkáte si? Jeho ošetření můžete klidně provést opět až po odeslání požadavku. Dokud totiž neskončí první volání vaší funkce, nikdy nemůže dojít k jejímu opětovnému vyvolání, i kdyby mezi tím uživatel klikl tisíckrát.

Na začátku funkce tak stačí jen ověřit nějakou proměnnou, jestli už k jednomu odeslání nedošlo (což zabere minimum času), a teprve na konci funkce tuto proměnnou nastavit a případně provést další akce, jako je vypnutí nebo skrytí tlačítka:

function loadContent(container, url) {
    var $container, $spinner;

    if (container.loading) {
        return; //už se načítá
    }

    //... odeslání requestu
    //... příprava proměnných a změna DOM

    //na konci funkce
    container.loading = true;
} 

Závěr

Před odesláním požadavku na server provádějte jen ty nejnutnější akce, jako je příprava parametrů a ošetření dvojitého odeslání.

Vše ostatní provádějte až poté, kdy stejně čekáte na odpověď.

Navíc se snažte co nejvíce akcí, nutných pro zpracování odpovědi, připravit během čekání, aby se odpověď zpracovala co nejrychleji.

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *

Tato stránka používá Akismet k omezení spamu. Podívejte se, jak vaše data z komentářů zpracováváme..