XMLHttpRequest
Obiekt XMLHttpRequest istnieje w Javascript nierozerwalnie od momentu powstania Ajax i służy do nawiązywania dynamicznych połączeń XHR.
W dzisiejszych czasach mamy dla niego nowszy zamiennik w postaci Fetch i to prawdopodobnie z niego będziesz głównie korzystał. Zasady działania tu i tam są podobne, dlatego warto poznać obydwa, zwłaszcza, że Fetch nie zadziała na każdej przeglądarce.
Nawiązujemy połączenie
Pierwszą czynnością jaką musimy wykonać to skonfigurowanie połączenia za pomocą metody open(typ, url, [async, login*, password*]). Metoda ta przyjmuje 3 atrybuty: typ połączenia (get, post, put, patch, delete), adres do którego się łączymy, oraz trzeci określający czy nasze połączenie ma być asynchroniczne czy synchroniczne. Dodatkowo możemy tutaj podać login i hasło w przypadku Basic HTTP Authentication.
Po wstępnej konfiguracji wysyłamy nasze połączenie za pomocą metody send().
const xhr = new XMLHttpRequest();
//typ połączenia, url, czy połączenie asynchroniczne
xhr.open("GET", "https://jsonplaceholder.typicode.com/posts", true);
xhr.send();Metoda send() służy do wysyłania połączenia na serwer. Jeżeli w danym połączeniu wysyłamy dane, musimy je podać jako atrybut tej metody. W przypadku gdy nie wysyłamy żadnych danych nie podajemy nic, a domyślnie zostanie użyta wartość null. Niektórzy programiści wizualnie wstawiają tutaj null dla zabezpieczenia w przypadku starych przeglądarek (1):
//GET
xhr.send(null);
//POST
xhr.send(formData);Czekamy na odpowiedź
Powyższe połączenie (funkcja open()) za pomocą 3 parametru ustawiliśmy na asynchroniczne. Spowoduje to, że reszta kodu nie będzie blokowana przed wykonaniem.
Aby wykryć moment kiedy dane połączenie się zakończyło, musimy podłączyć do obiektu nasłuchiwanie odpowiedniego zdarzenia.
Dla obiektu XMLHttpRequest najważniejsze zdarzenia to:
error
Błąd nawiązywania połączenia (np. przerwało połączenie)
progress
Postęp wczytywania
Mamy też zdarzenia abort (anulowanie połączenia), timeout (przekroczony maksymalny czas połączenia), loadstart/loadend (rozpoczęcie i zakończenie połączenia - nie ważne czy pozytywne) - ale nie są one aż tak często wykorzystywane.
Każde połączenie może zakończyć się sukcesem (zostały pobrane dane) lub się nie udać (np. brak połączenia z internetem). W tym pierwszym przypadku zadziała zdarzenie load, natomiast w tym drugim zdarzenie error.
Zdarzenie load oznacza tylko to, że nasze połączenie zakończyło się pozytywnie i dostaliśmy w odpowiedzi jakieś dane. Zwrócone dane mogą zawierać odpowiedź której oczekiwaliśmy, ale też odpowiedź ze strony jakiegoś błędu (np. status 404, 500, czy 301 w przypadku przekierowania). Przed przystąpieniem do operowania na właściwych danych, dobrą manierą jest sprawdzenie, czy status naszego połączenia wynosi 200 i czy mamy realne dane:
Gdy połączenie zakończy się pozytywnie, możemy skorzystać z dodatkowych właściwości:
status
zawiera status połączenia. Status 200 oznacza pozytywne zwrócenie danych, 400 brak strony, 500 błąd serwera itp.
statusText
zawiera status połączenia w formie tekstowej. Dla 200 będzie to OK, dla 404 "Not Found", dla 403 "Forbidden" itp.
Kliknij i sprawdź w konsoli
Właściwość response zawiera zwrócone dane. Domyślnie są one w formacie tekstowym. Takie dane powinniśmy skonwertować na odpowiedni format:
Możemy też przed wykonaniem połączenia ustawić typ danych jakich oczekujemy, dzięki czemu unikniemy ręcznej konwersji. Służy do tego właściwość responseType, która przyjmuje wartości:
"text"
zwraca dane w formacie string
"arraybuffer"
zwraca dane jako ArrayBuffer
"blob"
zwraca dane jako Blob
"document"
zwraca dane jako dokument XML
"json"
zwraca dane jako JSON
Kliknij i sprawdź w konsoli
Wysyłanie danych
Aby wysłać dane na serwer, musimy je podać jako wartość dla funkcji send().
Wysyłając dane na serwer możemy je wysłać w dowolnym formacie - np. jako zwykły prosty tekst.
W większości sytuacji zamiast pojedynczego tekstu będziemy chcieli wysłać porcję danych, które serwer odczyta jako oddzielne zmienne. Żeby to zrobić, musimy naszą wysyłkę odpowiednio zakodować, ale też poinformować serwer w jakim formacie te dane mu wysyłamy.
Robimy to ustawiając za pomocą funkcji setRequestHeader() nagłówek Content-Type na odpowiedni format.
Jest to sytuacja podobna do komunikacji przeglądarka-serwer. Gdy przeglądarka wysyła na serwer jakieś dane (z formularza na stronie), informuje serwer o typie wysyłanych danych właśnie za pomocą tego nagłówka. Podobnie jest gdy coś ściągamy z internetu - wtedy serwer podobnym nagłówkiem informuje naszą przeglądarkę jakie dane jej wysłał. Takie informowanie dzieje się automatycznie, natomiast w przypadku dynamicznych połączeń za pomocą Javascript musimy taki nagłówek ustawiać ręcznie (chociaż są od tego wyjątki).
Do wysyłania danych najczęściej używane jest jedno z trzech formatów:
application/x-www-form-urlencoded- dane są kodowane do postaci przypominającej URLmultipart/form-data- dane są kodowane do postaci blokówapplication/json- najczęściej używane przy komunikacji z wszelakimi API
W HTML5 istnieje też kodowanie text/plain. Typ ten stosowany jest automatycznie, gdy wysyłamy dane tekstowe i nie ustawimy żadnego typu (tak jak w powyższym przykładzie). Dane takie wysyłane są w postaci czytelnej dla człowieka (jako zwykły tekst). Niekoniecznie jednak forma ta jest czytelna dla serwera, dlatego kodowania tego używa się raczej tylko w fazie testowania połączeń.
Wysyłanie prostych danych tekstowych
Pierwsze z wymienionych kodowań - application/x-www-form-urlencoded - możemy stosować w przypadkach gdy chcemy przesłać kilka prostych danych tekstowych.
Dane wysyłane w ten sposób powinny być zakodowane do postaci przypominającej adres URL składający się z par klucz=wartość rozdzielonych znakiem &, a dodatkowo wszelkie znaki nie alfanumeryczne muszą być tutaj odpowiednio zakodowane:
Wysyłanie danych złożonych
Podobnie do klasycznych formularzy jeżeli wysyłane dane zawierają pliki (dane binarne), powinniśmy skorzystać z kodowania multipart/form-data.
Dane takie są przesyłane w formie specjalnie zakodowanych kawałków (body part), gdzie każdy ma informacje o przesyłanej rzeczy (jego content-type, długość itp) dlatego nadają się do przesyłania nie tylko danych tekstowych, ale także danych różnego rodzaju - np. tekstów wraz z plikami.
Żeby nie było niedomówień. Format ten możemy także wykorzystać do wysyłania prostych danych tekstowych (tak jak w przypadku application/x-www-form-urlencoded). W wielu przypadkach jednak proste dane typu klucz-wartość zakodowane w ten sposób będą miały większą długość niż te same dane zakodowane za pomocą application/x-www-form-urlencoded. Jeżeli jednak różnica w wielkości (często bardzo mała) jest dla ciebie do zaakceptowania, śmiało sięgaj po to kodowanie, ponieważ jest zazwyczaj łatwiejsze w użyciu.
Formatu tego nie kodujemy ręcznie, natomiast używamy do tego interfejsu FormData(). Dodatkowym plusem jest tutaj to, że nie musimy ustawiać nagłówka, ponieważ jest on ustawiany automatycznie.
Interfejs FormData udostępnia nam kilka przydatnych metod.
formData.append(name, value)- dodaje nową wartość o danej nazwieformData.append(name, blob, fileName)– dodaje wartość o danej nazwie tak jakby było pobrane bezpośrednio z pola <input type="file">, trzeci parametr oznacza nazwę plikuformData.delete(name)– usuwa wartość o danej nazwieformData.get(name)– pobiera wartość pola o danej nazwieFormData.has(name)– zwraca true/false w zależności od tego czy dana wartość istniejeformData.set(name)- nadpisuje wartość o danej nazwie
W codziennej pracy będziesz zapewne głównie używał pokazanej powyżej append().
W przypadku gdy chcesz wysłać dane z całego formularza, nie musisz dodawać wartość każdego pola oddzielnie. Wystarczy przekazać do FormData cały formularz:
Wysyłanie danych w formacie JSON
Kolejny bardzo często stosowany format to application/json. Jest on najczęściej stosowany w komunikacji z RestAPI. Całych plików tutaj nie prześlemy, ponieważ wysyłane dane to odpowiednio zakodowany tekst.
Do zakodowania danych skorzystamy z funkcji JSON.stringify():
Postęp pobierania i wysyłania danych
Jedną z cech odróżniającą XMLHttpRequest od jego młodszego brata - Fetch - jest to, że w przypadku XMLHttpRequest możemy reagować na zmianę postępu wysyłania i pobierania danych.
Obie czynności są do siebie bardzo podobne. Różnicą jest to, że w przypadku wysyłania danych odwołujemy się do właściwości upload obiektu XMLHttpRequest, natomiast w przypadku ściągania danych odwołujemy się bezpośrednio do tego obiektu.
Sprawdzanie postępu przy wysyłaniu danych jest raczej proste, ponieważ przeglądarka zna wielkość wysyłanego pliku a i bez problemu może sprawdzić, ile danych zostało wysłanych. Jedyną rzeczą, którą musimy zrobić to podłączyć się pod zdarzenie progress właściwości upload. Dla zabezpieczenia czy rzeczywiście przeglądarka ma dostęp do tych danych stosujemy właściwość lengthComputable:

W przypadku postępu ściągania danych wykonujemy podobne działanie jak powyżej. Tym razem w odwołaniu pomijamy właściwość upload:
Niestety takie wyliczenie ściąganych danych nie zawsze będzie możliwe, ponieważ dość często przeglądarka nie wie dokładnie ile danych zostanie wysłanych z serwera. Żeby takie wyliczenie było możliwe, serwer powinien zwrócić nam długość odpowiedzi w nagłówku Content-Length:

Dodatkowo ściągnięte dane nie pojawią się automatycznie na stronie, a i nie będziemy mogli za pomocą javascriptu zapisać ich na dysk użytkownika. Rozwiązaniem może tutaj być ściągnięcie danych pod postacią "blob", a następnie stworzenie na ich podstawie linku, który będzie otwierał dynamicznie stworzony plik.
XMLHttpRequest i reakcja na wczytanie danych
Gdybyśmy chcieli stworzyć funkcję wczytującą dane, powinniśmy w jakiś sposób zareagować na jej zakończenie. Możemy tutaj zastosować jedną z poznanych wcześniej technik.
Co byś nie napisał, wychodzi sporo kodu. Dla niektórych zbyt sporo. Jak zobaczysz za chwilę fetch niweluje ten problem - a to głównie dlatego, że sam z siebie zwraca obietnicę, na którą możemy od razu zareagować.
Last updated