Introduzione alla Fetch API
Nel panorama dello sviluppo web moderno, l'interazione tra il client (browser) e il server è fondamentale. Per anni, lo standard de facto per effettuare richieste asincrone è stato XMLHttpRequest (XHR). Sebbene potente, XHR era noto per la sua sintassi verbosa e la gestione complicata dei callback, che spesso portava al cosiddetto "callback hell".
La Fetch API, introdotta con le specifiche di ES6, è arrivata per modernizzare questo processo. Fetch fornisce un'interfaccia JavaScript più potente e flessibile per effettuare richieste HTTP. Basata sulle Promise, permette di scrivere codice asincrono in modo molto più lineare e leggibile, integrandosi perfettamente con la sintassi async/await.
Perché usare Fetch invece di librerie esterne come Axios? Sebbene Axios offra funzionalità aggiuntive (come l'intercettazione delle richieste o la trasformazione automatica dei dati in JSON), Fetch è nativamente supportato da tutti i browser moderni. Questo significa meno dipendenze nel progetto, bundle più leggeri e un'aderenza agli standard web che garantisce longevità al codice.
Fondamenti: Come funziona una richiesta Fetch
Il cuore della Fetch API è la funzione globale fetch(). Questa funzione accetta un parametro obbligatorio (l'URL della risorsa) e un parametro opzionale (un oggetto di configurazione).
Il ciclo di vita di una Promise
Quando chiamiamo fetch(), il browser non restituisce immediatamente i dati del server, ma una Promise. Questa promessa rappresenta un'operazione che sarà completata in futuro.
È fondamentale capire che fetch() risolve la promessa non appena riceve una risposta dal server, indipendentemente dal codice di stato HTTP. Ciò significa che se il server risponde con un errore 404 (Not Found) o 500 (Internal Server Error), la promessa di fetch() risulterà comunque "risolta" (fulfilled). L'errore viene catturato solo se c'è un problema di rete o se la richiesta è stata bloccata (ad esempio da un errore di CORS).
Esempio di base: Lettura di dati (GET)
Vediamo come effettuare una semplice richiesta GET per recuperare dati da un'API pubblica. In questo esempio, utilizzeremo l'API di JSONPlaceholder per simulare il recupero di un post.
async function getPostData(postId) {
const url = `https://jsonplaceholder.typicode.com/posts/${postId}`;
try {
// 1. Effettua la richiesta di rete
const response = await fetch(url);
// 2. Verifica se la risposta è OK (status 200-299)
if (!response.ok) {
throw new Error(`Errore HTTP! Stato: ${response.status}`);
}
// 3. Estrai il corpo della risposta come JSON
const data = await response.json();
console.log('Dati ricevuti:', data);
return data;
} catch (error) {
console.error('Si è verificato un errore durante il fetch:', error);
}
}
// Esecuzione della funzione
getPostData(1);
Spiegazione del codice:
async/await: Usiamo queste parole chiave per rendere il codice asincrono simile a quello sincrono, evitando l'uso di.then()concatenati.response.ok: Questa proprietà è un booleano che ètruesolo se il codice di stato HTTP è compreso tra 200 e 299. È il modo corretto di gestire gli errori lato server con Fetch.response.json(): Questo metodo è esso stesso asincrono perché deve leggere l'intero stream di dati della risposta e convertirlo in un oggetto JavaScript.
Gestione Avanzata: Metodi POST, PUT e DELETE
Mentre le richieste GET sono semplici, le operazioni di scrittura richiedono l'invio di dati al server. Per farlo, dobbiamo passare un oggetto di configurazione come secondo argomento a fetch().
Inviare dati con POST
Quando inviamo dati (ad esempio per creare un nuovo utente), dobbiamo specificare il metodo, gli header (per dire al server che stiamo inviando JSON) e il corpo della richiesta (body).
async function createPost(postData) {
const url = 'https://jsonplaceholder.typicode.com/posts';
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN_HERE' // Esempio di header di autenticazione
},
body: JSON.stringify(postData) // I dati devono essere convertiti in stringa JSON
};
try {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`Errore durante la creazione: ${response.status}`);
}
const result = await response.json();
console.log('Post creato con successo:', result);
} catch (error) {
console.error('Errore POST:', error);
}
}
// Utilizzo
createPost({
title: 'Imparare Fetch API',
body: 'Questo è un articolo approfondito sulla programmazione web.',
userId: 1
});
Analisi tecnica:
JSON.stringify(): Il corpo della richiesta non può essere un oggetto JavaScript; deve essere una stringa. Per questo motivo utilizziamoJSON.stringify().Content-Type: Senza l'headerapplication/json, molti server non saprebbero come interpretare il corpo della richiesta e restituirebbero un errore 400 o 415.
Gestione degli Errori e Timeout
Uno dei problemi principali degli sviluppatori intermediate è l'eccessiva fiducia nel try...catch. Come menzionato, fetch non fallisce su errori HTTP. Inoltre, le richieste di rete possono "appendersi" (hang) se il server non risponde, bloccando potenzialmente l'esperienza utente.
Implementazione di un Timeout con AbortController
Per evitare che una richiesta rimanga aperta all'infinito, possiamo usare AbortController. Questo oggetto permette di annullare una richiesta in corso.
async function fetchWithTimeout(url, timeout = 5000) {
const controller = new AbortController();
const signal = controller.signal;
// Imposta un timer per annullare la richiesta dopo X millisecondi
const timer = setTimeout(() => controller.abort(), timeout);
try {
const response = await fetch(url, { signal });
clearTimeout(timer); // Pulisci il timer se la risposta arriva in tempo
if (!response.ok) throw new Error('Errore server');
return await response.json();
} catch (error) {
if (error.name === 'AbortError') {
console.error('La richiesta è scaduta (timeout)');
} else {
console.error('Errore di rete:', error);
}
}
}
Questo pattern è essenziale per applicazioni di livello enterprise dove la resilienza è prioritaria rispetto alla semplice funzionalità.
Esempi Pratici: Casi d'Uso Reali
1. Caricamento di un'immagine via Blob
Fetch non serve solo per il JSON. Può gestire qualsiasi tipo di dato, inclusi immagini, PDF e file binari, utilizzando il metodo .blob().
async function loadImage(imageUrl) {
const response = await fetch(imageUrl);
const blob = await response.blob();
const objectURL = URL.createObjectURL(blob);
const img = document.createElement('img');
img.src = objectURL;
document.body.appendChild(img);
}
2. Implementazione di un sistema di Pagination
In un'applicazione reale, raramente scarichiamo tutti i dati in una volta. Utilizziamo i query parameters per richiedere "pagine" di dati.
async function fetchPage(pageNumber) {
const response = await fetch(`https://api.example.com/items?_page=${pageNumber}&_limit=10`);
return await response.json();
}
Errori Comuni e FAQ
Perché ricevo un errore di CORS?
CORS (Cross-Origin Resource Sharing) è un meccanismo di sicurezza dei browser. Se provi a fare fetch verso un dominio diverso da quello in cui è ospitata la tua pagina, il browser invia una richiesta di pre-controllo (preflight). Se il server non risponde con gli header corretti (es. Access-Control-Allow-Origin: *), il browser bloccherà la risposta.
Soluzione: Il CORS va configurato sul server, non sul client. Se non hai accesso al server, puoi usare un proxy per i test.
response.json() può essere chiamato due volte?
No. Il corpo della risposta è un stream. Una volta che lo hai letto con .json(), .text() o .blob(), lo stream è consumato. Se provi a chiamarlo di nuovo, riceverai un errore: TypeError: Failed to execute 'json' on 'Response': body stream already read.
Soluzione: Se hai bisogno dei dati in più formati, clona la risposta usando response.clone() prima di leggerla.
Prossimi Passi
Ora che hai padronggiato le basi e le tecniche avanzate della Fetch API, puoi esplorare concetti più complessi per rendere le tue applicazioni ancora più performanti:
- Intercettori Custom: Crea un wrapper attorno a
fetch()per aggiungere automaticamente token di autenticazione a ogni richiesta o per gestire globalmente gli errori 401 (Unauthorized). - Caching con Service Workers: Impara come usare l'API Cache per memorizzare le risposte di Fetch e rendere la tua app disponibile offline (PWA).
- Parallelismo con
Promise.all(): Se devi effettuare più richieste indipendenti, non usareawaitin sequenza. UsaPromise.all([fetch1, fetch2])per lanciarle simultaneamente e ridurre i tempi di caricamento. - GraphQL: Se le API REST diventano troppo limitate, prova a usare Fetch per inviare query GraphQL, dove specifichi esattamente quali campi desideri ricevere dal server.
La Fetch API è lo strumento fondamentale per ogni sviluppatore web moderno. Comprendere a fondo come gestire gli stream, i timeout e gli errori di rete ti permetterà di costruire interfacce utente fluide, reattive e professionali.