Komunikacja HTTP w JavaScript

Komunikacja HTTP w JavaScript

Opublikowano Kategorie Backend, Frontend, JavaScriptCzas czytania 8min

Obecnie, zarówno na frontendzie, jak i na backendzie, ciężko byłoby znaleźć aplikację, która nie wykorzystuje protokołu HTTP. W tym artykule dowiesz się, jak w aplikacjach JavaScript możesz komunikować się z innymi aplikacjami z wykorzystaniem protokołu HTTP. Aby w pełni skorzystać z wiedzy zawartej w tym artykule, rekomenduję zapoznanie się z artykułem wprowadzającym do tematu REST API, gdzie jeden z rozdziałów poświęcony został protokołowi HTTP.

Klient HTTP

Do komunikacji z serwerem HTTP potrzebny będzie klient HTTP. W JavaScript do dyspozycji mamy wiele klientów, których możliwości są ze sobą porównywalne. Zarówno w środowisku przeglądarki, jak i w Node.js (od wersji 18) do dyspozycji są wbudowane klienty HTTP. Możesz również postawić na rozwiązania w postaci dedykowanej zależności. W dalszej części tego artykułu przedstawię Ci najczęściej wykorzystywane klienty HTTP w JavaScript.

W przedstawionych przykładach wykorzystane zostało Rick and Morty API. Jeśli potrzebujesz przetestować przedstawione klienty HTTP, możesz również skorzystać z dziesiątek innych, przykładowych HTTP API.

Do zwiększenia wygody pracy przydadzą Ci się jeszcze dwa elementy. Pierwszym i najważniejszym jest dokumentacja. Bez dokumentacji ciężko będzie Ci pracować z jakimkolwiek API. Drugim przydatnym elementem będzie graficzny klient HTTP. Nie jest on konieczny do pracy, ale czasami łatwiej jest wykonać zapytanie, korzystając np. z Postmana czy Insomnii niż pisać kod. Zestawienie popularnych aplikacji do komunikacji z wykorzystaniem protokołu HTTP znajdziesz w dedykowanym wpisie na blogu.

XMLHttpRequest

XMLHttpRequest to wbudowany obiekt, który umożliwia wykonywanie zapytań HTTP. Wbrew nazwie, umożliwia on pracę nie tylko z formatem danych XML. Mimo że są obecnie wygodniejsze w użyciu rozwiązania, to i tak warto znać to rozwiązanie. Jeżeli Twoja aplikacja wymaga wsparcia dla starych przeglądarek lub jest leciwa i jej przepisanie nie wchodzi w grę, XMLHttpRequest może stanowić jedyną dostępną opcję. Aby skorzystać z XMLHttpRequest w środowisku Node.js konieczne będzie skorzystanie z zewnętrznej zależności.

Inicjalizacja obiektu XMLHttpRequest wymaga jedynie stworzenia nowej instancji za pomocą new XMLHttpRequest(). Mając już instancję obiektu, trzeba zainicjalizować stworzony obiekt, korzystając z metody open. Wymaganymi argumentami są metoda HTTP i URL zasobu. Kolejne parametry pozwalają zdefiniować:

Oprócz metody open, obiekt XMLHttpRequest umożliwia nasłuchiwanie na zdarzenia. Do najbardziej użytecznych zdarzeń można zaliczyć:

  • load — emitowane gdy zapytanie się wykona i odpowiedź została już pobrana;
  • error — emitowane, gdy zapytanie zwróciło błąd;
  • progress — emitowane okresowo w trakcie wykonywania zapytania. Można je wykorzystać przykładowo do śledzenia postępu zapytania. Może być użyteczne, gdy np. potrzeba pobrać z serwera duży plik i wyświetlić pasek postępu.

Z właściwości obiektu XMLHttpRequest warto również wrócić uwagę na:

  • responseType — pozwala ustawić oczekiwany typ odpowiedzi. Co istotne, ustawienie typu odpowiedzi wymaga przeprowadzenia zapytania w trybie asynchronicznym;
  • status — zwraca kod odpowiedzi;
  • readyState — zwraca stan zapytania.

UNSENT = 0; // zapytanie oczekuje na wysłanie
OPENED = 1; // otwarto połączenie
HEADERS_RECEIVED = 2; // otrzymano nagłówki
LOADING = 3; // ładowanie odpowiedzi zapytania
DONE = 4; // zapytanie wykonano

Mając już gotowe zapytanie, można je wysłać metodą send. Przykładowy kod wysyłający zapytanie z wykorzystaniem XMLHttpRequest znajdziesz poniżej. Rezultatem uruchomienia poniższego przykładu będzie wylogowanie w konsoli statusu zapytania i odpowiedzi w formacie JSON. W przypadku błędu wylogowany zostanie błąd. Możesz to sprawdzić, zmieniając na przykład URL przekazany do metody open.


function getLocations() {
    const xmlHttpRequest = new XMLHttpRequest();
 
    xmlHttpRequest.onload = () => {
        console.log( 'Status:', xmlHttpRequest.status );
        console.log( 'Content:', xmlHttpRequest.response );
    };

    xmlHttpRequest.onerror = error => { 
        console.log( 'Network Error', error );
    };
  
    xmlHttpRequest.open( 'GET', 'https://rickandmortyapi.com/api/location' );
    xmlHttpRequest.responseType = 'json';
    xmlHttpRequest.send();
}

getLocations();

Fetch API

Fetch API jest kolejnym wbudowanym w JavaScript mechanizmem umożliwiającym wykonywanie zapytań HTTP. W przeciwieństwie do XMLHttpRequest Fetch API wykorzystuje mechanizm Promise, co pozwala na pisanie znacznie bardziej czytelnego i zgrabnego kodu. Wykorzystanie Fetch API sprowadza się do wywołania metody fetch, gdzie jedynym wymaganym argumentem jest URL zasobu. Domyślną metodą HTTP wykorzystywaną przez fetch jest GET. Chcąc zdefiniować dodatkowe parametry zapytania, takie jak metoda czy nagłówki można wykorzystać obiekt konfiguracyjny przekazany jako drugi argument. Fetch API umożliwia również skorzystanie z AbortControllera, który pozwala na przerwanie połączenia HTTP w trakcie jego wykonywania.

Wywołanie metody fetch powoduje zwrócenie obiektu Promise, który po rozwiązaniu zwraca obiekt Response. Z obiektu Response można uzyskać m.in. status zapytania, czy jego rezultat. Do uzyskania rezultatu zapytania w formacie JSON można wykorzystać asynchroniczną metodę json. Przykład kodu odpytujący Rick and Morty API z wykorzystaniem Fetch API znajdziesz poniżej.


( async () => {
    try {
        const response = await fetch(
          'https://rickandmortyapi.com/api/location'
        );
  
        const locations = await response.json();
  
        console.log( response.status, locations );
    } catch( error ) {
        console.log( error.message );
    }
} )();

Chcąc skorzystać z Fetch API w środowisku Node.js w wersjach starszych niż Node 18 konieczne będzie skorzystanie z paczki node-fetch. Od Node 18 dostępne jest wbudowane Fetch API bazujące na kliencie HTTP Undici. Więcej o implementacji Fetch API w Node.js możesz przeczytać w changelogu dla Node 18.

Axios

Axios jest biblioteką stworzoną na potrzeby komunikacji z wykorzystaniem HTTP. Podobnie jak Fetch API Axios wykorzystuje obiekty Promise. Najbardziej podstawowe wykorzystanie Axiosa zakłada zaimportowanie go w miejscu docelowym i wywołaniu na nim odpowiedniej metody.

Metody Axiosa odpowiadają metodom HTTP. Rezultatem wywołania metod Axiosa jest Promise, który po rozwiązaniu zwraca obiekt zawierający wszystkie informacje związane z wykonanym zapytaniem i odpowiedzią. Jedynym wymaganym argumentem jest URL zasobu. W drugim, opcjonalnym argumencie można przekazać dodatkowe szczegóły zapytania, na przykład parametry, czy wartości przesyłane w body.


import 'axios' from axios;

( async () => {
    try {
        const response = await axios.get(
            'https://rickandmortyapi.com/api/location'
        );

        console.log( response.data );
    } catch ( error ) {
        console.log( error.message );
    }
} )();

Zaimportowany obiekt również jest funkcją, którą można wywołać w kodzie. Zachowuje się wtedy podobnie do metody fetch. Możliwe jest też przekazanie wszystkich parametrów zapytania z wykorzystaniem obiektu konfiguracyjnego.


import 'axios' from axios;

( async () => {
    const instance = axios.create( {
        baseURL: 'https://rickandmortyapi.com/api/'
    } );

    try {
        const response = await instance.get( 'location' );

        console.log( response.data );
    } catch ( error ) {
        console.log( error.message );
    }
} )();

Czymś, co moim zdaniem przemawia za Axiosem, jest możliwość zdefiniowania bazowego klienta. Dzięki temu możliwe jest ustawienie części parametrów zapytania.  Można w ten sposób ustawić np. domyślne nagłówki HTTP, adres bazowy czy limit czasowy zapytania.


import 'axios' from axios;

( async () => {
    try {
        const response1 = await axios(
            { url: 'https://rickandmortyapi.com/api/location' }
        );

        const response2 = await axios(
            'https://rickandmortyapi.com/api/location'
        );

        console.log( response1.data, response2.data );
    } catch ( error ) {
        console.log( error.message );
    }
} )();

Undici

Na sam koniec zostawiłem klienta HTTP dedykowanego środowisku Node.js. Do wykonania najprostszego zapytania HTTP, biblioteka Undici udostępnia metodę request. Podobnie jak w innych klientach przekazanie URL zasobu spowoduje wysłanie zapytania typu GET pod wskazany adres. Przekazanie obiektu konfiguracyjnego otwiera przed użytkownikiem szersze możliwości. Co ciekawe, Undici implementuje również metodę fetch zgodną z tą oferowaną przez Fetch API.


import { request } from 'undici';

( async () => {
    const { statusCode, body } = await request(
        'https://rickandmortyapi.com/api/location'
    );

    console.log( 'Status: ', statusCode );
    console.log( 'Response: ', await body.json() );
} )();

Podsumowanie

Zasada działania wszystkich przedstawionych klientów HTTP jest taka sama – wykonaj zapytanie i przetwórz odpowiedź. Różnice w ich wykorzystaniu dotyczą drobnych szczegółów implementacyjnych. Osobiście jestem zwolennikiem wykorzystywania wbudowanych klientów HTTP, ale w najpowszechniejszym zastosowaniu każdy z przedstawionych klientów sprawdzi się dobrze. Mam nadzieję, że wpis okazał się dla Ciebie przydatny. Daj znać jakiego klienta HTTP wykorzystujesz!

Źródła i materiały dodatkowe

*Artykuł jest odświeżoną wersją wpisu z 2018 roku. 

Dominik Szczepaniak

Zawodowo Senior Software Engineer w CKSource. Prywatnie bloger, fan włoskiej kuchni, miłośnik jazdy na rowerze i treningu siłowego.

Inne wpisy, które mogą Cię zainteresować

Zapisz się na mailing i odbierz e-booka

Zapisując się na mój mailing, otrzymasz darmowy egzemplarz e-booka 106 Pytań Rekrutacyjnych Junior JavaScript Developer! Będziesz też otrzymywać wartościowe treści i powiadomienia o nowych wpisach na skrzynkę e-mail.

Subscribe
Powiadom o
guest

2 komentarzy
oceniany
najnowszy najstarszy
Inline Feedbacks
View all comments
Anna
Anna
1 rok temu

Jako laik dziękuję za przybliżenie API