W nowoczesnym Parku Jurajskim, oprócz lokalnych systemów monitorowania i kontroli, potrzebujemy również komunikować się z zewnętrznymi systemami i API. Czy to sprawdzanie prognoz pogodowych dla parku, synchronizacja danych z globalnymi bazami dinozaurów czy integracja z systemami rezerwacji dla odwiedzających - wszystko to wymaga solidnej metody wykonywania żądań HTTP. W tym ćwiczeniu poznamy API Fetch, nowoczesny interfejs do wykonywania żądań sieciowych w JavaScript.
Fetch API to nowoczesny, wbudowany interfejs JavaScript do wykonywania żądań HTTP. Zastępuje starsze podejścia, takie jak XMLHttpRequest, oferując bardziej ergonomiczny i potężny zestaw narzędzi oparty na Promise.
Główne zalety Fetch API:
Podstawowa składnia Fetch jest bardzo prosta:
1fetch(url)
2 .then(response => response.json())
3 .then(data => console.log(data))
4 .catch(error => console.error('Błąd:', error));Przyjrzyjmy się, jak moglibyśmy wykorzystać Fetch w naszym Parku Jurajskim:
1// Pobieranie danych o dinozaurze z API
2function pobierzDaneDinozaura(id) {
3 return fetch(`https://api.jurassic-park.com/dinosaurs/${id}`)
4 .then(response => {
5 // Sprawdzenie, czy odpowiedź jest poprawna
6 if (!response.ok) {
7 throw new Error(`Błąd HTTP: ${response.status}`);
8 }
9 return response.json();
10 })
11 .then(data => {
12 console.log(`Pobrano dane dinozaura: ${data.name}`);
13 return data;
14 })
15 .catch(error => {
16 console.error(`Nie udało się pobrać danych dinozaura #${id}:`, error);
17 throw error;
18 });
19}
20
21// Użycie funkcji
22pobierzDaneDinozaura(42)
23 .then(dino => {
24 console.log(`Dinozaur #42: ${dino.name}, Gatunek: ${dino.species}`);
25 aktualizujPanelInformacyjny(dino);
26 })
27 .catch(error => {
28 wyświetlKomunikatBłędu(`Nie udało się pobrać informacji o dinozaurze: ${error.message}`);
29 });Fetch domyślnie wykonuje żądanie GET, ale możemy łatwo określić inny typ żądania i przesłać dane:
1// Aktualizacja danych dinozaura za pomocą żądania PUT
2async function aktualizujDaneDinozaura(id, noweDane) {
3 try {
4 const response = await fetch(`https://api.jurassic-park.com/dinosaurs/${id}`, {
5 method: 'PUT',
6 headers: {
7 'Content-Type': 'application/json',
8 'Authorization': `Bearer ${apiToken}`
9 },
10 body: JSON.stringify(noweDane)
11 });
12
13 if (!response.ok) {
14 throw new Error(`Błąd podczas aktualizacji: ${response.status}`);
15 }
16
17 const zaktualizowanyDinozaur = await response.json();
18 console.log(`Zaktualizowano dane dinozaura #${id}`);
19 return zaktualizowanyDinozaur;
20 } catch (error) {
21 console.error(`Błąd aktualizacji dinozaura #${id}:`, error);
22 throw error;
23 }
24}
25
26// Tworzenie nowego rekordu dinozaura za pomocą żądania POST
27async function dodajNowegoDinozaura(dane) {
28 try {
29 const response = await fetch('https://api.jurassic-park.com/dinosaurs', {
30 method: 'POST',
31 headers: {
32 'Content-Type': 'application/json',
33 'Authorization': `Bearer ${apiToken}`
34 },
35 body: JSON.stringify(dane)
36 });
37
38 if (!response.ok) {
39 throw new Error(`Błąd podczas dodawania dinozaura: ${response.status}`);
40 }
41
42 const nowyDinozaur = await response.json();
43 console.log(`Dodano nowego dinozaura #${nowyDinozaur.id}`);
44 return nowyDinozaur;
45 } catch (error) {
46 console.error('Błąd dodawania dinozaura:', error);
47 throw error;
48 }
49}
50
51// Usunięcie dinozaura za pomocą żądania DELETE
52async function usuńDinozaura(id) {
53 try {
54 const response = await fetch(`https://api.jurassic-park.com/dinosaurs/${id}`, {
55 method: 'DELETE',
56 headers: {
57 'Authorization': `Bearer ${apiToken}`
58 }
59 });
60
61 if (!response.ok) {
62 throw new Error(`Błąd podczas usuwania dinozaura: ${response.status}`);
63 }
64
65 // W przypadku DELETE serwer może nie zwracać danych
66 if (response.status !== 204) { // No Content
67 const data = await response.json();
68 console.log(`Usunięto dinozaura #${id}:`, data);
69 return data;
70 }
71
72 console.log(`Usunięto dinozaura #${id}`);
73 return { success: true, id };
74 } catch (error) {
75 console.error(`Błąd usuwania dinozaura #${id}:`, error);
76 throw error;
77 }
78}Kiedy wywołujemy funkcję
fetch(), zwraca ona Promise, który rozwiązuje się do obiektu Response. Ten obiekt zawiera wszystkie informacje o odpowiedzi HTTP:1fetch('https://api.jurassic-park.com/status')
2 .then(response => {
3 console.log('Status:', response.status); // np. 200
4 console.log('Status OK:', response.ok); // true dla statusu 200-299
5 console.log('Typ zawartości:', response.headers.get('content-type'));
6 console.log('Wszystkie nagłówki:', [...response.headers.entries()]);
7
8 return response.json(); // Parsowanie odpowiedzi jako JSON
9 })
10 .then(data => {
11 console.log('Dane:', data);
12 });Obiekt
Response oferuje różne metody do przetwarzania danych odpowiedzi:| Metoda | Opis | |--------|------| | response.json() | Parsuje zawartość jako JSON i zwraca Promise z wynikiem | | response.text() | Zwraca Promise z zawartością jako tekst | | response.blob() | Zwraca Promise z zawartością jako obiekt Blob (dane binarne) | | response.formData() | Parsuje zawartość jako FormData | | response.arrayBuffer() | Zwraca Promise z zawartością jako ArrayBuffer |
Ważne jest, aby pamiętać, że możesz wywołać tylko jedną z tych metod na obiekcie
Response. Body odpowiedzi może być przetworzone tylko raz, a próba wielokrotnego wywołania np. response.json() spowoduje błąd.Ważną cechą Fetch API jest to, że Promise zwrócony przez
fetch() nie zostanie odrzucony dla błędów HTTP, takich jak 404 lub 500. Zostanie odrzucony tylko w przypadku problemów sieciowych, takich jak brak połączenia.Aby prawidłowo obsługiwać błędy HTTP, należy sprawdzić właściwość
response.ok lub kod statusu response.status:1fetch('https://api.jurassic-park.com/dinosaurs/999')
2 .then(response => {
3 if (!response.ok) {
4 // Tworzenie błędu z kodem statusu
5 throw new Error(`Błąd HTTP: ${response.status}`);
6 }
7 return response.json();
8 })
9 .then(data => {
10 console.log('Dane:', data);
11 })
12 .catch(error => {
13 console.error('Błąd:', error.message);
14 // Tutaj możemy obsłużyć zarówno błędy HTTP, jak i błędy sieci
15 });Można też rozszerzyć tę logikę, aby lepiej obsługiwać różne kody statusu:
1fetch('https://api.jurassic-park.com/dinosaurs/999')
2 .then(response => {
3 if (response.status === 404) {
4 throw new Error('Dinozaur nie znaleziony');
5 } else if (response.status === 401) {
6 throw new Error('Brak autoryzacji - zaloguj się ponownie');
7 } else if (response.status === 403) {
8 throw new Error('Brak uprawnień do tej operacji');
9 } else if (!response.ok) {
10 throw new Error(`Błąd HTTP: ${response.status}`);
11 }
12 return response.json();
13 })
14 .then(/* ... */)
15 .catch(/* ... */);Fetch przyjmuje opcjonalny drugi argument, który pozwala na dostosowanie żądania:
1fetch(url, {
2 method: 'POST', // Metoda HTTP (GET, POST, PUT, DELETE, itd.)
3 headers: { // Nagłówki HTTP
4 'Content-Type': 'application/json',
5 'Authorization': 'Bearer token123'
6 },
7 body: JSON.stringify(data), // Ciało żądania (dla POST, PUT, itd.)
8 mode: 'cors', // Tryb CORS
9 credentials: 'include', // Czy dołączać ciasteczka
10 cache: 'no-cache', // Sposób obsługi cache
11 redirect: 'follow', // Sposób obsługi przekierowań
12 referrer: 'no-referrer', // Nagłówek referrer
13 signal: abortController.signal // Do anulowania żądania
14})1async function wykonajZłożoneŻądanie() {
2 const abortController = new AbortController();
3 const signal = abortController.signal;
4
5 // Ustawienie timeout dla żądania
6 const timeout = setTimeout(() => {
7 abortController.abort();
8 }, 5000); // 5 sekund
9
10 try {
11 const response = await fetch('https://api.jurassic-park.com/data', {
12 method: 'POST',
13 headers: {
14 'Content-Type': 'application/json',
15 'Authorization': `Bearer ${apiToken}`,
16 'X-Jurassic-Version': '1.2'
17 },
18 body: JSON.stringify({
19 query: 'velociraptor',
20 limit: 10,
21 withDetails: true
22 }),
23 mode: 'cors',
24 credentials: 'include', // Dołącz ciasteczka
25 cache: 'no-cache', // Nie używaj cache
26 redirect: 'follow', // Automatycznie podążaj za przekierowaniami
27 referrer: 'no-referrer',
28 signal // Do anulowania żądania
29 });
30
31 clearTimeout(timeout);
32
33 if (!response.ok) {
34 throw new Error(`Błąd HTTP: ${response.status}`);
35 }
36
37 return await response.json();
38 } catch (error) {
39 clearTimeout(timeout);
40
41 if (error.name === 'AbortError') {
42 console.error('Żądanie przerwane po upływie czasu oczekiwania');
43 throw new Error('Przekroczono czas oczekiwania na odpowiedź');
44 }
45
46 console.error('Błąd żądania:', error);
47 throw error;
48 }
49}AbortController to interfejs, który pozwala na anulowanie żądań Fetch. Jest szczególnie przydatny do implementacji timeoutów lub gdy użytkownik chce anulować długotrwałe żądanie:
1function pobierzDużeDaneDinozaurów(query) {
2 // Utworzenie kontrolera anulowania
3 const controller = new AbortController();
4 const { signal } = controller;
5
6 // Ustawienie timeoutu
7 const timeoutId = setTimeout(() => {
8 controller.abort();
9 }, 10000); // 10 sekund
10
11 // Przycisk anulowania dla użytkownika
12 const cancelButton = document.getElementById('cancel-button');
13 cancelButton.addEventListener('click', () => {
14 controller.abort();
15 clearTimeout(timeoutId);
16 cancelButton.disabled = true;
17 cancelButton.textContent = 'Anulowano';
18 });
19
20 // Wykonanie żądania z sygnałem anulowania
21 return fetch(`https://api.jurassic-park.com/search?q=${query}`, { signal })
22 .then(response => {
23 clearTimeout(timeoutId);
24 if (!response.ok) {
25 throw new Error(`Błąd HTTP: ${response.status}`);
26 }
27 return response.json();
28 })
29 .then(data => {
30 cancelButton.disabled = true;
31 cancelButton.textContent = 'Zakończono';
32 return data;
33 })
34 .catch(error => {
35 clearTimeout(timeoutId);
36
37 if (error.name === 'AbortError') {
38 console.log('Żądanie zostało anulowane');
39 // Obsługujemy anulowanie jako normalny przepływ, nie jako błąd
40 return { cancelled: true };
41 }
42
43 // Dla innych błędów, przekazujemy dalej
44 throw error;
45 });
46}Czasami potrzebujemy wykonać wiele żądań jednocześnie. Możemy to zrobić łącząc Fetch z Promise.all:
1async function pobierzDaneDlaWybiegu(wybiegId) {
2 try {
3 // Definiujemy wszystkie żądania
4 const dinozauryPromise = fetch(`https://api.jurassic-park.com/paddocks/${wybiegId}/dinosaurs`).then(r => r.json());
5 const statusPromise = fetch(`https://api.jurassic-park.com/paddocks/${wybiegId}/status`).then(r => r.json());
6 const historiaPromise = fetch(`https://api.jurassic-park.com/paddocks/${wybiegId}/history`).then(r => r.json());
7
8 // Wykonujemy wszystkie żądania równolegle
9 const [dinozaury, status, historia] = await Promise.all([
10 dinozauryPromise,
11 statusPromise,
12 historiaPromise
13 ]);
14
15 // Łączymy dane w jeden obiekt
16 return {
17 wybiegId,
18 dinozaury,
19 status,
20 historia,
21 aktualizacja: new Date()
22 };
23 } catch (error) {
24 console.error(`Błąd pobierania danych dla wybiegu #${wybiegId}:`, error);
25 throw error;
26 }
27}Fetch może obsługiwać różne formaty danych, nie tylko JSON:
1fetch('https://api.jurassic-park.com/logs')
2 .then(response => response.text())
3 .then(text => {
4 console.log('Logi:', text);
5 document.getElementById('logs').textContent = text;
6 });1// Pobieranie obrazu i wyświetlanie go
2fetch('https://api.jurassic-park.com/dinosaurs/42/image')
3 .then(response => response.blob())
4 .then(blob => {
5 const imageUrl = URL.createObjectURL(blob);
6 document.getElementById('dino-image').src = imageUrl;
7 });
8
9// Pobieranie i zapisywanie pliku (w środowisku przeglądarki)
10fetch('https://api.jurassic-park.com/reports/monthly.pdf')
11 .then(response => response.blob())
12 .then(blob => {
13 const url = URL.createObjectURL(blob);
14 const a = document.createElement('a');
15 a.href = url;
16 a.download = 'raport-miesięczny.pdf';
17 document.body.appendChild(a);
18 a.click();
19 document.body.removeChild(a);
20 URL.revokeObjectURL(url);
21 });Fetch może również obsługiwać wysyłanie formularzy:
1// Wysyłanie danych formularza jako JSON
2document.getElementById('dino-form').addEventListener('submit', async function(e) {
3 e.preventDefault();
4
5 const form = e.target;
6 const formData = new FormData(form);
7
8 // Konwersja FormData do obiektu JavaScript
9 const formObject = {};
10 for (const [key, value] of formData.entries()) {
11 formObject[key] = value;
12 }
13
14 try {
15 const response = await fetch('https://api.jurassic-park.com/dinosaurs', {
16 method: 'POST',
17 headers: {
18 'Content-Type': 'application/json'
19 },
20 body: JSON.stringify(formObject)
21 });
22
23 if (!response.ok) {
24 throw new Error(`Błąd HTTP: ${response.status}`);
25 }
26
27 const result = await response.json();
28 alert(`Dinozaur dodany pomyślnie! ID: ${result.id}`);
29 form.reset();
30 } catch (error) {
31 console.error('Błąd podczas wysyłania formularza:', error);
32 alert(`Błąd: ${error.message}`);
33 }
34});
35
36// Wysyłanie formularza z plikami
37document.getElementById('dino-upload-form').addEventListener('submit', async function(e) {
38 e.preventDefault();
39
40 const form = e.target;
41 const formData = new FormData(form);
42
43 try {
44 const response = await fetch('https://api.jurassic-park.com/dinosaurs/import', {
45 method: 'POST',
46 body: formData // FormData jest automatycznie ustawiane jako 'multipart/form-data'
47 });
48
49 if (!response.ok) {
50 throw new Error(`Błąd HTTP: ${response.status}`);
51 }
52
53 const result = await response.json();
54 alert(`Import zakończony! Dodano ${result.count} dinozaurów.`);
55 } catch (error) {
56 console.error('Błąd podczas importu:', error);
57 alert(`Błąd: ${error.message}`);
58 }
59});CORS to mechanizm bezpieczeństwa, który kontroluje, czy strona może wykonywać żądania do zasobów na innej domenie. Domyślnie, żądania między domenami są zabronione przez politykę "same-origin" przeglądarek.
1fetch('https://api.different-domain.com/data', {
2 mode: 'cors' // Domyślna wartość
3})1fetch('https://api.different-domain.com/protected-data', {
2 mode: 'cors',
3 credentials: 'include' // Wysyła ciasteczka cross-origin
4})Opcje dla parametru
credentials:omit: Nie wysyłaj ciasteczek (domyślnie)same-origin: Wysyłaj ciasteczka tylko do tej samej domenyinclude: Wysyłaj ciasteczka również do innych domenFetch pozwala na kontrolowanie, jak przeglądarka używa swojego cache:
1// Ignorowanie cache, zawsze wykonaj żądanie do serwera
2fetch(url, {
3 cache: 'no-store'
4})
5
6// Najpierw sprawdź cache, jeśli nie ma tam odpowiedzi, wykonaj żądanie
7fetch(url, {
8 cache: 'default'
9})
10
11// Najpierw sprawdź cache, ale zweryfikuj z serwerem, czy odpowiedź jest aktualna
12fetch(url, {
13 cache: 'no-cache'
14})
15
16// Używaj tylko cache, nie wykonuj żądania sieciowego
17fetch(url, {
18 cache: 'only-if-cached',
19 mode: 'same-origin' // Wymagane dla 'only-if-cached'
20})1// Uniwersalna funkcja do wykonywania żądań API
2async function apiRequest(endpoint, options = {}) {
3 const apiBaseUrl = 'https://api.jurassic-park.com';
4 const url = `${apiBaseUrl}${endpoint}`;
5
6 // Domyślne opcje
7 const defaultOptions = {
8 headers: {
9 'Content-Type': 'application/json',
10 'Authorization': `Bearer ${localStorage.getItem('apiToken')}`
11 },
12 mode: 'cors'
13 };
14
15 // Łączenie opcji, z priorytetem dla tych przekazanych
16 const mergedOptions = {
17 ...defaultOptions,
18 ...options,
19 headers: {
20 ...defaultOptions.headers,
21 ...(options.headers || {})
22 }
23 };
24
25 // Automatyczne przekształcanie body do JSON, jeśli to obiekt
26 if (mergedOptions.body && typeof mergedOptions.body === 'object' && !(mergedOptions.body instanceof FormData)) {
27 mergedOptions.body = JSON.stringify(mergedOptions.body);
28 }
29
30 try {
31 const response = await fetch(url, mergedOptions);
32
33 // Obsługa różnych kodów statusu
34 if (response.status === 401) {
35 // Odświeżanie tokenu lub przekierowanie do logowania
36 await refreshAuthToken();
37 // Ponowienie żądania z nowym tokenem
38 return apiRequest(endpoint, options);
39 }
40
41 if (response.status === 204) { // No Content
42 return { success: true };
43 }
44
45 if (!response.ok) {
46 // Próba pobrania błędu w formacie JSON
47 try {
48 const errorData = await response.json();
49 throw new Error(errorData.message || `Błąd HTTP: ${response.status}`);
50 } catch (e) {
51 // Jeśli nie możemy sparsować błędu jako JSON, użyj prostego komunikatu
52 throw new Error(`Błąd HTTP: ${response.status}`);
53 }
54 }
55
56 // Sprawdź Content-Type, aby określić format odpowiedzi
57 const contentType = response.headers.get('content-type');
58 if (contentType && contentType.includes('application/json')) {
59 return await response.json();
60 } else if (contentType && contentType.includes('text/')) {
61 return await response.text();
62 } else {
63 return await response.blob();
64 }
65 } catch (error) {
66 // Obsługa błędów sieci i innych
67 console.error(`Błąd żądania API (${endpoint}):`, error);
68
69 // Niestandardowy obiekt błędu z dodatkowymi informacjami
70 const apiError = new Error(error.message);
71 apiError.isNetworkError = !window.navigator.onLine;
72 apiError.endpoint = endpoint;
73 apiError.originalError = error;
74
75 throw apiError;
76 }
77}
78
79// Przykład użycia
80async function pobierzDinozaury() {
81 const dinozaury = await apiRequest('/dinosaurs?limit=50');
82 return dinozaury;
83}
84
85async function aktualizujDinozaura(id, dane) {
86 return apiRequest(`/dinosaurs/${id}`, {
87 method: 'PUT',
88 body: dane
89 });
90}
91
92async function importujDaneZPliku(plik) {
93 const formData = new FormData();
94 formData.append('file', plik);
95
96 return apiRequest('/import', {
97 method: 'POST',
98 body: formData,
99 headers: {
100 // Usunięcie Content-Type, aby przeglądarka mogła ustawić prawidłowy z boundary
101 'Content-Type': undefined
102 }
103 });
104}1/**
2 * Wykonuje funkcję z automatycznymi ponownymi próbami
3 * @param {Function} fn - Funkcja do wykonania, która zwraca Promise
4 * @param {Object} options - Opcje
5 * @param {number} options.maxRetries - Maksymalna liczba ponownych prób
6 * @param {number} options.retryDelay - Opóźnienie przed ponowną próbą (ms)
7 * @param {Function} options.shouldRetry - Funkcja określająca, czy ponawiać próbę
8 * @param {number} options.backoffFactor - Mnożnik opóźnienia (backoff)
9 * @returns {Promise} - Promise z wynikiem funkcji lub ostatnim błędem
10 */
11async function withRetry(fn, options = {}) {
12 const {
13 maxRetries = 3,
14 retryDelay = 1000,
15 shouldRetry = (error) => true,
16 backoffFactor = 2
17 } = options;
18
19 let lastError;
20
21 for (let attempt = 0; attempt <= maxRetries; attempt++) {
22 try {
23 return await fn();
24 } catch (error) {
25 lastError = error;
26
27 if (attempt === maxRetries || !shouldRetry(error)) {
28 break;
29 }
30
31 const delay = retryDelay * Math.pow(backoffFactor, attempt);
32 console.log(`Próba ${attempt + 1} nie powiodła się. Ponowienie za ${delay}ms...`);
33 await new Promise(resolve => setTimeout(resolve, delay));
34 }
35 }
36
37 throw lastError;
38}
39
40// Przykład użycia z Fetch API
41async function pobierzDaneZChwilowegoAPI() {
42 return withRetry(
43 () => fetch('https://api.jurassic-park.com/unstable-endpoint')
44 .then(response => {
45 if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
46 return response.json();
47 }),
48 {
49 maxRetries: 5,
50 retryDelay: 500,
51 backoffFactor: 1.5,
52 shouldRetry: (error) => {
53 // Ponawiaj próbę tylko dla błędów 5xx (błędy serwera) lub błędów sieci
54 return error.message.includes('HTTP Error: 5') ||
55 error instanceof TypeError;
56 }
57 }
58 );
59}1// Prosty system cache dla żądań GET
2const apiCache = {
3 cache: new Map(),
4 ttl: 60000, // czas życia cache w ms (1 minuta)
5
6 async get(url, options = {}) {
7 const cacheKey = `${url}|${JSON.stringify(options)}`;
8 const cachedItem = this.cache.get(cacheKey);
9
10 // Jeśli mamy ważny cached item, użyj go
11 if (cachedItem && Date.now() < cachedItem.expiry) {
12 console.log(`Używam cache dla: ${url}`);
13 return cachedItem.data;
14 }
15
16 // W przeciwnym razie wykonaj żądanie
17 console.log(`Wykonuję żądanie dla: ${url}`);
18 const response = await fetch(url, options);
19
20 if (!response.ok) {
21 throw new Error(`Błąd HTTP: ${response.status}`);
22 }
23
24 const data = await response.json();
25
26 // Zapisz w cache, jeśli to żądanie GET
27 if (!options.method || options.method === 'GET') {
28 this.cache.set(cacheKey, {
29 data,
30 expiry: Date.now() + this.ttl
31 });
32 }
33
34 return data;
35 },
36
37 invalidate(urlPattern) {
38 // Usuń z cache wszystkie klucze pasujące do wzorca
39 for (const key of this.cache.keys()) {
40 if (key.includes(urlPattern)) {
41 this.cache.delete(key);
42 }
43 }
44 },
45
46 clear() {
47 this.cache.clear();
48 }
49};
50
51// Przykład użycia
52async function pobierzDaneDinozaura(id) {
53 return apiCache.get(`https://api.jurassic-park.com/dinosaurs/${id}`);
54}
55
56async function zaktualizujDinozaura(id, dane) {
57 const result = await fetch(`https://api.jurassic-park.com/dinosaurs/${id}`, {
58 method: 'PUT',
59 headers: { 'Content-Type': 'application/json' },
60 body: JSON.stringify(dane)
61 }).then(r => r.json());
62
63 // Unieważnij cache dla tego dinozaura i list dinozaurów
64 apiCache.invalidate(`/dinosaurs/${id}`);
65 apiCache.invalidate('/dinosaurs?');
66
67 return result;
68}Fetch API jest dostępny we wszystkich nowoczesnych przeglądarkach, a od wersji 18 również natywnie w Node.js. Jednak istnieje kilka ograniczeń i problemów z kompatybilnością:
Nie obsługuje postępu żądania - Fetch nie ma wbudowanej obsługi postępu ładowania/wysyłania. Dla takich przypadków czasem lepiej jest użyć XMLHttpRequest.
Nie posiada natywnego timeoutu - Timeout trzeba implementować za pomocą AbortController.
Nie odrzuca Promise dla błędów HTTP - Nawet jeśli serwer zwróci kod błędu, Fetch Promise zostanie rozwiązany. Trzeba sprawdzać response.ok.
W starszych przeglądarkach może być konieczne użycie polyfilla, takiego jak
whatwg-fetch lub biblioteki takiej jak axios, która zapewnia jednolite API na różnych środowiskach.Choć Fetch API jest dostępna natywnie, istnieją alternatywy, które mogą zaoferować dodatkowe funkcje:
Axios - Popularna biblioteka, która zapewnia:
jQuery.ajax() - W starszych projektach używających jQuery
XMLHttpRequest - Starsza, bardziej szczegółowa alternatywa, która oferuje więcej kontroli, np. nad postępem żądań
Fetch jest idealny, gdy:
Rozważ alternatywy, gdy:
Zawsze sprawdzaj response.ok
1fetch(url).then(response => {
2 if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
3 return response.json();
4});Ustaw odpowiednie nagłówki
1fetch(url, {
2 headers: {
3 'Content-Type': 'application/json',
4 'Accept': 'application/json'
5 }
6});Używaj AbortController dla timeoutów
1const controller = new AbortController();
2const timeout = setTimeout(() => controller.abort(), 5000);
3
4fetch(url, { signal: controller.signal })
5 .then(response => /* ... */)
6 .catch(error => /* ... */)
7 .finally(() => clearTimeout(timeout));Ostrożnie z przetwarzaniem body
1// ŹLE - próba wielokrotnego użycia body
2fetch(url)
3 .then(response => {
4 const text = response.text(); // To konsumuje body!
5 const json = response.json(); // Błąd - body już zostało zużyte!
6 return /* ... */;
7 });
8
9// DOBRZE - sklonuj response, jeśli potrzebujesz go użyć wielokrotnie
10fetch(url)
11 .then(response => {
12 const clone = response.clone();
13 return response.text().then(text => {
14 return clone.json().then(json => {
15 return { text, json };
16 });
17 });
18 });Używaj async/await dla czytelności
1// Zamiast łańcuchowania .then()
2async function fetchData() {
3 try {
4 const response = await fetch(url);
5 if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
6 const data = await response.json();
7 return data;
8 } catch (error) {
9 console.error('Błąd:', error);
10 throw error;
11 }
12}Fetch API jest potężnym, wbudowanym narzędziem do wykonywania żądań HTTP w JavaScript. Jego główne zalety to Promise-based API, prostota użycia i wszechstronność. Chociaż ma pewne ograniczenia w porównaniu do bardziej zaawansowanych bibliotek, jak Axios, dla większości przypadków użycia jest całkowicie wystarczający.
W Parku Jurajskim, gdzie stabilna komunikacja między różnymi systemami jest kluczowa dla bezpieczeństwa odwiedzających i dinozaurów, Fetch API pozwala na niezawodną wymianę danych między systemami monitorującymi, bazami danych i interfejsami użytkownika.
W następnym ćwiczeniu przyjrzymy się, jak łączyć Fetch API z innymi technikami asynchronicznymi, aby tworzyć jeszcze bardziej zaawansowane systemy do zarządzania naszym cyfrowym Parkiem Jurajskim.