$.Deferred & Promise tutorial – zarządzanie asynchronicznymi wywołaniami w jQuery

jQuery oferuje bardzo dobry mechanizm do zarządzania asynchronicznymi wywołaniami (np. AJAX), który pozwala uchronić nas przed pisanie tzw. spaghetti code. API do obsługi składa się zaledwie z kilku metod, ale dzięki przemyślanej konstrukcji niesie ze sobą bardzo duże możliwości.

Warto również zaznaczyć, że koncepcja zastosowana w jQuery ma o wiele dłuższą historię i jest ona rozpowszechniona na różne języki programowania i środowiska – nie jest jednak tak znana, jak na to zasługuje.

Należy też wspomnieć, że sama implementacja mechanizmu w jQuery ulegała drobnym zmianom między poszczególnymi wersjami biblioteki, ale od wersji 1.8 przybrała już chyba postać ostateczną.

Nie opiszę tu wszystkich cech i możliwości tego mechanizmu, ale pokaże na tylko jednym przykładzie, czemu warto z niego korzystać.

CEL:
Chcemy wykonać 2 zapytania AJAX i na podstawie zwróconych danych zadecydować o dalszym działaniu.

SŁABE ROZWIĄZANIE:

$.post( 'ajax/respond1.php', function(data1) {
    $.post( 'ajax/respond2.php', function(data2) {
        if( data1.status > data2.status ) {
            alert( data1.status );
        } else {
            alert( data2.status );
        }
    });
});

Powyższe rozwiązanie jest słabe z co najmniej 2-ch powodów:

  • Wolne – Drugie wywołanie AJAX rozpoczyna się dopiero po zakończeniu pierwszego; czasami jest to pożądane działanie, ale bardzo często jest to wada
  • Nieskalowalne – przy większej liczbie zagnieżdżeń kod staje się nieczytelny.

DOBRE ROZWIĄZANIE:

var loadingStatus( sUrl ) {
    var defer = $.Deferred();
    $.post( sUrl, function(data) {
        defer.resolve( data.status );
    }
    return defer;
};
 
var loadingStatus1 = loadingStatus( 'ajax/respond1.php' );
var loadingStatus2 = loadingStatus( 'ajax/respond2.php' );
 
$.when(
    loadingStatus1,
    loadingStatus2
).then( function(status1, status2) {
    if( status1 > status2 ) {
        alert( status1 );
    } else {
        alert( status2 );
    }
});

Powyższe rozwiązanie może jest dłuższe, ale jest o wiele lepsze, gdyż zapytania AJAX wywołują się równocześnie oraz kod jest o wiele bardziej czytelny nawet przy wielu wywołaniach. Ale dzięki temu, że jQuery wspiera natywnie obiekty Deferred, to można jeszcze lepiej.

SUPER ROZWIĄZANIE:

var loadingStatus1 = $.post( 'ajax/respond1.php' );
var loadingStatus2 = $.post( 'ajax/respond2.php' );
 
$.when(
    loadingStatus1,
    loadingStatus2
).then( function(status1, status2) {
    if( status1 > status2 ) {
        alert( status1 );
    } else {
        alert( status2 );
    }
});