Lekcja 15: AJAX w jQuery - Obiekty Deferred i Promises (.done()
, .fail()
, .always()
)
Metody AJAX w jQuery (w tym $.ajax()
i metody skrótowe jak $.get()
, $.getJSON()
) zwracają specjalny obiekt zwany jqXHR (jQuery XMLHttpRequest). Obiekt ten implementuje interfejs Promise, co pozwala na bardziej elastyczne i czytelne zarządzanie operacjami asynchronicznymi za pomocą metod .done()
, .fail()
i .always()
, zamiast tradycyjnych callbacków w opcjach konfiguracyjnych.
Interfejs Promise w jQuery AJAX
Zamiast przekazywać funkcje success
, error
i complete
w obiekcie konfiguracyjnym $.ajax()
, możemy dołączyć je do obiektu jqXHR zwróconego przez wywołanie AJAX:
let zapytanie = $.ajax({
url: "/api/dane",
dataType: "json"
});
// Rejestrowanie callbacków za pomocą metod Promise
zapytanie.done(function(data, textStatus, jqXHR) {
// Wykonywane po sukcesie (odpowiednik `success`)
console.log("Sukces (done):", data);
});
zapytanie.fail(function(jqXHR, textStatus, errorThrown) {
// Wykonywane po błędzie (odpowiednik `error`)
console.error("Błąd (fail):", textStatus, errorThrown);
});
zapytanie.always(function(dataOrJqXHR, textStatus, jqXHROrErrorThrown) {
// Wykonywane zawsze po zakończeniu (sukces lub błąd) (odpowiednik `complete`)
console.log("Zawsze (always): Zapytanie zakończone.");
});
// Można też łączyć wywołania (chaining)
$.ajax({ url: "/api/inne-dane" })
.done(function(dane) { /* obsługa sukcesu */ })
.fail(function() { /* obsługa błędu */ })
.always(function() { /* wykonaj zawsze */ });
Zalety Używania .done()
, .fail()
, .always()
:
- Czytelność: Oddzielenie konfiguracji zapytania od logiki obsługi odpowiedzi może poprawić czytelność kodu.
- Elastyczność: Można dołączyć wiele handlerów do tego samego zdarzenia (np. kilka funkcji
.done()
). - Kompozycja: Umożliwia łatwiejsze zarządzanie wieloma zapytaniami AJAX (np. za pomocą
$.when()
). - Obsługa błędów w metodach skrótowych: Jest to standardowy sposób obsługi błędów dla metod takich jak
$.get()
czy$.getJSON()
, które nie przyjmują callbackuerror
jako argumentu.
Przykład z $.getJSON()
$("#pobierz-komentarze").on("click", function() {
let postId = $("#post-id-input").val();
$("#status").text("Ładowanie komentarzy...");
$.getJSON("https://jsonplaceholder.typicode.com/comments", { postId: postId })
.done(function(komentarze) {
let listaHtml = "";
komentarze.forEach(kom => listaHtml += `- ${kom.name}: ${kom.body}
`);
listaHtml += "
";
$("#lista-komentarzy").html(listaHtml);
$("#status").text("Komentarze załadowane.");
})
.fail(function(jqXHR, textStatus, errorThrown) {
$("#lista-komentarzy").empty();
$("#status").text(`Błąd ładowania: ${textStatus} - ${errorThrown}`).css("color", "red");
})
.always(function() {
console.log("Zakończono próbę pobrania komentarzy.");
});
});
Łączenie Wielu Zapytań AJAX za pomocą $.when()
Czasami potrzebujemy wykonać kilka niezależnych zapytań AJAX i zareagować dopiero wtedy, gdy wszystkie się zakończą (pomyślnie). Do tego służy metoda $.when()
.
$.when()
przyjmuje jako argumenty jeden lub więcej obiektów Deferred/Promise (np. zwróconych przez $.ajax()
). Zwraca nowy obiekt Promise, który zostanie rozwiązany (resolved), gdy wszystkie przekazane Promise zostaną rozwiązane.
let zapytanieUzytkownikow = $.getJSON("/api/users");
let zapytanieProduktow = $.getJSON("/api/products");
let zapytanieUstawien = $.getJSON("/api/settings");
$.when(zapytanieUzytkownikow, zapytanieProduktow, zapytanieUstawien)
.done(function(wynikUser, wynikProd, wynikUst) {
// Ten callback wykona się, gdy WSZYSTKIE 3 zapytania zakończą się sukcesem
// Argumenty funkcji `done` to tablice, gdzie pierwszy element [0] zawiera dane odpowiedzi
let uzytkownicy = wynikUser[0];
let produkty = wynikProd[0];
let ustawienia = wynikUst[0];
console.log("Wszystkie dane załadowane:", uzytkownicy, produkty, ustawienia);
// Tutaj można zbudować interfejs używając wszystkich danych
})
.fail(function() {
// Ten callback wykona się, jeśli KTÓREKOLWIEK z zapytań się nie powiedzie
console.error("Wystąpił błąd podczas ładowania danych.");
});
Ważne: Argumenty przekazywane do funkcji .done()
po użyciu $.when()
są tablicami. Każda tablica odpowiada jednemu z przekazanych Promise i zawiera argumenty, które normalnie trafiłyby do pojedynczego .done()
(czyli [data, textStatus, jqXHR]
).
Zadanie praktyczne
Użyj API JSONPlaceholder. Chcemy pobrać dane użytkownika (/users/2
) oraz jego posty (/posts?userId=2
) i wyświetlić je dopiero, gdy oba zapytania się powiodą.
Stwórz plik HTML z przyciskiem "Pobierz Dane Usera 2" i div-em z ID "dane-kompletne".
Używając $.when()
oraz $.getJSON()
:
- Po kliknięciu przycisku, zainicjuj dwa zapytania
$.getJSON()
: jedno po dane użytkownika 2, drugie po jego posty. - Użyj
$.when()
, przekazując mu oba obiekty Promise zwrócone przez$.getJSON()
. - W funkcji
.done()
dołączonej do$.when()
: - Pobierz dane użytkownika z pierwszego argumentu (
argument1[0]
) i dane postów z drugiego argumentu (argument2[0]
). - Wyświetl w div-ie "dane-kompletne" imię użytkownika oraz listę tytułów jego postów.
- W funkcji
.fail()
dołączonej do$.when()
, wyświetl komunikat o błędzie w div-ie.
Pokaż rozwiązanie
HTML:
<!DOCTYPE html>
<html>
<head>
<title>Test $.when()</title>
</head>
<body>
<button id="pobierz-dane-usera2">Pobierz Dane Usera 2</button>
<div id="dane-kompletne" style="margin-top: 10px; border: 1px solid #ccc; padding: 10px; min-height: 100px;">
<!-- Tutaj pojawią się dane -->
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
<script>
$(function() {
$("#pobierz-dane-usera2").on("click", function() {
$("#dane-kompletne").html("Ładowanie danych...");
let zapytanieUsera = $.getJSON("https://jsonplaceholder.typicode.com/users/2");
let zapytaniePostow = $.getJSON("https://jsonplaceholder.typicode.com/posts", { userId: 2 });
$.when(zapytanieUsera, zapytaniePostow)
.done(function(wynikUser, wynikPosty) {
let user = wynikUser[0]; // Dane użytkownika są w pierwszym elemencie pierwszej tablicy
let posts = wynikPosty[0]; // Dane postów są w pierwszym elemencie drugiej tablicy
let html = `<h3>${user.name}</h3>`;
html += "<h4>Posty:</h4>";
posts.forEach(function(post) {
html += `- ${post.title}
`;
});
html += "
";
$("#dane-kompletne").html(html);
})
.fail(function() {
$("#dane-kompletne").html("<p style=\'color: red;\'>Błąd podczas ładowania danych.</p>");
});
});
});
</script>
</body>
</html>
Zadanie do samodzielnego wykonania
Stwórz przycisk "Pobierz Todos" i div z ID "lista-todos".
Używając $.ajax()
i metod .done()/.fail()/.always()
:
- Po kliknięciu przycisku, wykonaj zapytanie GET do
https://jsonplaceholder.typicode.com/todos?userId=1
. - Użyj
.done()
, aby wyświetlić listę zadań (title
) w div-ie "lista-todos". Oznacz wizualnie (np. przekreśleniem lub innym stylem) zadania, które są ukończone (completed: true
). - Użyj
.fail()
, aby wyświetlić komunikat błędu. - Użyj
.always()
, aby wypisać w konsoli "Zakończono zapytanie o todos.".
FAQ - jQuery AJAX Promises (.done()
, .fail()
, .always()
, $.when()
)
Czy mogę używać .done()
itp. razem z callbackami w $.ajax()
?
Tak, można mieszać oba podejścia. Jeśli zdefiniujesz np. callback success
w opcjach $.ajax()
oraz dołączysz funkcję .done()
do zwróconego obiektu jqXHR, obie funkcje zostaną wykonane po pomyślnym zakończeniu zapytania. Jednak dla spójności kodu zaleca się trzymanie jednego stylu.
Co jeśli jedno z zapytań w $.when()
się nie powiedzie?
Jeśli którekolwiek z zapytań przekazanych do $.when()
zakończy się błędem, zostanie wywołany callback .fail()
dołączony do $.when()
. Callback .done()
nie zostanie wykonany.
Czy $.when()
działa tylko z zapytaniami AJAX jQuery?
$.when()
może przyjmować dowolne obiekty implementujące interfejs Promise (posiadające metodę .then()
), a także inne wartości. Jeśli przekażesz wartość niebędącą Promise, $.when()
potraktuje ją jako natychmiast rozwiązaną Promise. Jest to przydatne do synchronizacji operacji AJAX z innymi operacjami.
Jaka jest różnica między .always()
a .then()
?
.always()
jest specyficzne dla implementacji Deferred/Promise w jQuery i jest wywoływane zawsze, niezależnie od sukcesu czy porażki. Standardowa metoda Promises .then(onFulfilled, onRejected)
pozwala zdefiniować osobne funkcje dla sukcesu (onFulfilled
) i porażki (onRejected
). jQuery również wspiera .then()
, które zachowuje się podobnie do standardu.
Czy powinienem używać natywnych Promises (ES6) zamiast jQuery Deferred?
Natywne Promises są standardem w nowoczesnym JavaScript i oferują nieco inną składnię (np. .then().catch()
). Jeśli pracujesz w środowisku wspierającym ES6+ i nie potrzebujesz specyficznych funkcji jQuery Deferred, używanie natywnych Promises jest często preferowane dla lepszej kompatybilności i zgodności ze standardami. Jednak obiekty jqXHR zwracane przez jQuery AJAX nadal dobrze integrują się z metodami .done()
, .fail()
, .always()
.