Fetch API

Nowe przeglądarki udostępniają nam Fetch API - nowy interfejs do dynamicznego pobierania zasobów.

Jako, że fetch jest dość nowy, w poniższych skryptach będziemy wykorzystywać nowe rozwiązania takie jak funkcja strzałkowa, template string itp.

Pobieranie danych

Do naszych testów skorzystamy z darmowego API mieszczącego się pod adresem https://jsonplaceholder.typicode.com/.

Po wejściu na tą stronę widzimy, że aby pobrać użytkowników, musimy połączyć się na adres https://jsonplaceholder.typicode.com/users.

Wykonajmy podstawowe połączenie w celu pobrania danych:


fetch('https://jsonplaceholder.typicode.com/users')
    .then(resp => {
        console.log(resp);
    })

Po odpaleniu fetch zwraca Promise, więc tak samo jak w tamtym rozdziale, możemy je obsłużyć za pomocą dostępnych dla Promisow metod - then, all i catch.

Po wywołaniu powyższego skryptu naszym oczom w konsoli pokaże się mniej więcej coś takiego:

response fetch

Czyli dostaliśmy odpowiedź. Jak widzisz, wśród właściwości mamy status 200, statusText, url itp.

Po takiej odpowiedzi będziemy mieli dostęp do różnych jej właściwości:


fetch('https://jsonplaceholder.typicode.com/users').then(resp => {
    console.log(resp.headers.get('Content-Type'));
    console.log(resp.headers.get('Date'));

    console.log(resp.status);
    console.log(resp.statusText);
    console.log(resp.type);
    console.log(resp.url);
    console.log(resp.body);
    ...
});

Właściwa odpowiedź jest przetrzymywana pod właściwością body. W konsoli debugera powyższy kod wyświetli nam obiekt ReadableStream. Aby zamienić go na odpowiedni format musimy zastosować odpowiednią metodę, która skonwertuje tą właściwość na dany format. W naszym przypadku oczekujemy json, więc zastosujmy metodę response.json(). Dla innych typów danych trzeba by użyć innych metod - np. dla tekstu response.text(), a dla grafik response.blob()


fetch('https://jsonplaceholder.typicode.com/users')
    .then(resp => resp.json())
    .then(resp => {
        console.log("Przykład 2:");
        console.log(resp);
    })

Naszym oczom w konsoli debugera ukaże się lista użytkowników. Zróbmy więc po niej prostą pętlę:


fetch('https://jsonplaceholder.typicode.com/users')
    .then(resp => resp.json())
    .then(resp => {
        console.log(resp);
        resp.forEach(user => {
            console.groupCollapsed(`Użytkownik ${user.id}`)
            console.log(`Nazwa: ${user.name}`);
            console.log(`Nazwa użytkownika: ${user.username}`);
            console.log(`Email: ${user.email}`);
            console.log(`Adres: ${user.address.city} ${user.address.street} ${user.address.zipcode}`);
            console.log(`WWW: ${user.website}`);
            console.groupEnd();
        })
    })

Wysyłanie danych

Żeby wysłać dane musimy je ustawić we właściwości body. Dane takie podobnie jak w przypadku XMLHttpRequest powinniśmy zakodowane w ciąg znaków.


fetch('...', {
        method: 'post',
        body: 'name=Marcin&surname=Nowak'
    })
    .then(res => res.json())
    .then(res => {
        console.log('Dodałem użytkownika:');
        console.log(res);
    })

Jeżeli nie chcemy ręcznie kodować takiego zapisu, skorzystajmy z formData:


const formData = new FormData();
formData.append('name', nameVal);
formData.append('surname', surnameVal);

fetch('...', {
        method: 'post',
        body: formData
    })
    .then(res => res.json())
    .then(res => {
        console.log('Dodałem użytkownika:');
        console.log(res);
    })

Sprawdźmy to w praktyce. Po wejściu na stronę https://jsonplaceholder.typicode.com/posts, widzimy, że każdy post składa się z id, title, userId i body. ID jest automatycznie zwiększane, więc musimy wysłać tylko pozostałe właściwości:


const formData = new FormData();
formData.append('title', 'Lorem ipsum');
formData.append('body', 'Lorem ipsum dolor sit amet consectetur...');
formData.append('userId', 1);

fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'post',
        body: formData
    })
    .then(res => res.json())
    .then(res => {
        console.log('Dodałem użytkownika:');
        console.log(res);
    })

Api do którego się łączymy nie zapisuje realnie użytkowników, ale tylko symuluje. W odpowiedzi dostaliśmy użytkownika o id:101, co oznacza, że zostało symulowane dodanie do bazy nowego użytkownika (normalnie było ich 100).

Czasami zajdzie potrzeba wysłania danych innego typu - np. JSON. Musimy wtedy ustawić odpowiedni nagłówek Content-Type, a nasze dane musimy zakodować do postaci JSON.


const ob = {
    title: 'Nazwa posta',
    body: 'Lorem ipsum dolor sit amet consectetur...',
    userId: 1
};

fetch('https://jsonplaceholder.typicode.com/posts', {
        method: 'post',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify(ob)
    })
    .then(res => res.json())
    .then(res => {
        console.log('Dodałem użytkownika:');
        console.log(res);
    })

Nagłówki takie możemy ustawić tak jak powyżej - ręcznie. Możemy też skorzystać z obiektu typu Header, który udostępnia nam dodatkowe metody do manipulacji pojedynczymi nagłówkami:


const ourHeaders = new Headers();

//dodajemy dodatkowe nagłówki
ourHeaders.append('Content-Type', 'text/plain');
ourHeaders.append('X-My-Custom-Header', 'CustomValue');

//czy powyższy obiekt ma dany nagłówek
ourHeaders.has('Content-Type'); // true

//pobieramy dany nagłówek
ourHeaders.get('Content-Type'); // "text/plain"

//ustawiamy nagłówek
ourHeaders.set('Content-Type', 'application/json');

//usuwamy nagłówek
ourHeaders.delete('X-My-Custom-Header');


fetch('https://jsonplaceholder.typicode.com/posts', {
    method: 'post',
    headers: ourHeaders,
    body: JSON.stringify(ob)
})

Po drugie musimy zamienić nasz obiekt na odpowiedni zapis. Wykorzystujemy tutaj JSON.stringify(). Podobną metodę stosowaliśmy do zapisu obiektów do customowych atrybutów

Błędy w połączeniu

Spróbujmy na początek połączyć się na błędny adres:


fetch('https://jsonplaceholder.typicode.com/kaszanka')
    .then(resp => {
        console.log('Odpowiedź:');
        console.dir(resp)
    })
    .catch(error => console.log('Błąd: ', error));

Teoretycznie wystąpił błąd, więc powinien się odpalić catch. Nic takiego jednak się nie stało, bo w konsoli debugera otrzymaliśmy odpowiedź prawie jak przy naszym pierwszym połączeniu. Różnice są w niektórych właściwościach:

response 404

Jak widzimy, status zmienił się na 404, statusText na "Not Found", a właściwość ok zmieniła się na false.

Aby obsłużyć błędne zapytania, musimy w then() obsłużyć powyższe właściwości:


fetch('https://jsonplaceholder.typicode.com/kaszanka')
    .then(resp => {
        if (resp.ok) {
            return response.json()
        } else {
            throw new Error('Wystąpił błąd połączenia!')
        }
    })
    .then(resp => {
        console.log(resp)
    })
    .catch(error => console.dir('Błąd: ', error));

Żeby jeszcze dokładniej poinformować użytkownika o wynikłym błędzie, możemy skorzystać z Promise, który po zwróceniu reject przeskoczy do catch:


fetch('https://jsonplaceholder.typicode.com/kaszanka')
    .then(resp => {
        .then(resp => {
            if (resp.ok) {
                return response.json()
            } else {
                return Promise.reject(resp)
            }
        })
        .then(resp => {
            console.log(resp)
        })
        .catch(error => {
            if (error.status === 404) {
                console.log('Błąd: żądany adres nie istnieje');
            }
        });
    });

Trening czyni mistrza

Poniżej zamieszczam kilka zadań, które w ramach ćwiczenia możesz wykonać:

  1. Przejdź na stronę http://mailtest.in/documentation/

    Twoim zadaniem jest:
    - stwórz formularz, który będzie miał jedno pole na email oraz button submit
    - podepnij się pod wysyłanie formularza i przerwij domyślną akcję.
    Za pomocą fetch wyślij dane na odpowiedni adres. Sprawdź na powyższej stronie jak ma on wyglądać. W zależności od odpowiedzi wyświetl w konsoli stosowny komunikat.

  2. Przejdź na stronę https://developers.google.com/books/docs/v1/using#WorkingVolumes
    Za pomocą fetch pobierz liste książek o tematyce "wiedzmin".

    Zrób pętlę po wynikach i wrzuć ładnie sformatowane dane do html. Dane niech zawierają:
    - tytuł książki
    - autorzy
    - liczbę podstron
    - link do poglądu
    - czy dostępne w formie pdf

    Do formatowania danych użyj template strings