Fetch API in JavaScript: esempi pratici
La Fetch API è l’interfaccia moderna per effettuare richieste HTTP/HTTPS in JavaScript. Offre una sintassi basata su Promise, supporta flussi, abort con AbortController
, caricamento file, CORS e molto altro. In questo articolo imparerai tutto ciò che ti serve per usarla in modo efficace nel browser.
Prima richiesta: GET di base
// Esegue una GET e converte la risposta in JSON
fetch('https://api.example.com/items')
.then(response => {
if (!response.ok) {
// response.ok è true per status 200–299
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}
return response.json(); // oppure .text(), .blob(), .arrayBuffer(), .formData()
})
.then(data => {
console.log('Dati:', data);
})
.catch(err => {
// Qui catturi errori di rete o quelli lanciati sopra
console.error('Errore nella fetch:', err);
});
Uso con async/await
async function loadItems() {
try {
const res = await fetch('https://api.example.com/items');
if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`);
const data = await res.json();
console.log(data);
} catch (err) {
console.error('Errore:', err);
}
}
loadItems();
Impostare header e query string
// Query string con URL e URLSearchParams
const url = new URL('https://api.example.com/search');
url.search = new URLSearchParams({ q: 'notebook', limit: 10 }).toString();
const headers = new Headers({
'Accept': 'application/json'
});
const res = await fetch(url, { headers });
if (!res.ok) throw new Error('Richiesta fallita');
const results = await res.json();
Inviare JSON: POST, PUT, PATCH
async function createItem(payload) {
const res = await fetch('https://api.example.com/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json', // indica JSON nel body
'Accept': 'application/json'
},
body: JSON.stringify(payload)
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
createItem({ title: 'Nuovo oggetto', price: 19.9 })
.then(console.log)
.catch(console.error);
Eliminare risorse: DELETE
const id = 42;
const res = await fetch(`https://api.example.com/items/${id}`, { method: 'DELETE' });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
Gestione degli errori
- Errori HTTP:
response.ok
èfalse
per status non 2xx. Non generano automaticamente eccezioni: devi controllare e lanciare manualmente. - Errori di rete: disconnessioni, CORS bloccato, nome host non risolvibile; questi generano eccezioni catturabili nel
catch
. - Parsing: se la risposta non è JSON valido,
response.json()
rigetta.
try {
const res = await fetch('/api');
if (!res.ok) {
const text = await res.text().catch(() => '');
throw new Error(`HTTP ${res.status}: ${text || res.statusText}`);
}
} catch (err) {
// err.name può essere 'TypeError' per errori rete/CORS
console.error(err);
}
Timeout e annullamento con AbortController
Fetch non ha timeout nativo; puoi abortire con AbortController
.
function fetchWithTimeout(resource, options = {}) {
const { timeout = 8000 } = options;
const controller = new AbortController();
const id = setTimeout(() => controller.abort('timeout'), timeout);
return fetch(resource, { ...options, signal: controller.signal })
.finally(() => clearTimeout(id));
}
try {
const res = await fetchWithTimeout('/lento', { timeout: 5000 });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
console.log(data);
} catch (err) {
if (err.name === 'AbortError') {
console.warn('Richiesta annullata per timeout');
} else {
console.error(err);
}
}
Ripetizioni con backoff esponenziale
async function fetchRetry(url, options = {}, retries = 3, backoff = 500) {
try {
const res = await fetch(url, options);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res;
} catch (err) {
if (retries > 0) {
await new Promise(r => setTimeout(r, backoff));
return fetchRetry(url, options, retries - 1, backoff * 2);
}
throw err;
}
}
CORS in breve
- CORS è controllato dal server con header come
Access-Control-Allow-Origin
. - Dal client puoi influire su
mode
('cors'
,'no-cors'
,'same-origin'
) e sucredentials
. - Le richieste con credenziali richiedono header server appropriati (
Access-Control-Allow-Credentials: true
).
const res = await fetch('https://api.altrodominio.com/data', {
mode: 'cors',
credentials: 'include' // cookie/autenticazione inviate se consentite
});
Opzioni principali di fetch()
method
,headers
,body
mode
:'cors'
,'no-cors'
,'same-origin'
cache
:'default'
,'no-store'
,'reload'
,'no-cache'
,'force-cache'
,'only-if-cached'
credentials
:'omit'
,'same-origin'
,'include'
redirect
:'follow'
,'error'
,'manual'
referrer
,referrerPolicy
,integrity
,keepalive
,signal
Request e Response
fetch()
accetta un Request
e ritorna un Response
.
const req = new Request('/api/items', {
method: 'GET',
headers: new Headers({ 'Accept': 'application/json' }),
cache: 'no-cache'
});
const res = await fetch(req);
console.log(res.status, res.headers.get('Content-Type'));
const clone = res.clone(); // consente letture multiple del body
const json = await res.json();
const text = await clone.text();
Scaricare file (Blob) e creare link
async function downloadCsv() {
const res = await fetch('/report.csv');
if (!res.ok) throw new Error('Download fallito');
const blob = await res.blob();
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'report.csv';
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
Caricare file con FormData
async function uploadFile(file) {
const form = new FormData();
form.append('avatar', file, file.name);
form.append('userId', '123');
const res = await fetch('/upload', {
method: 'POST',
body: form // niente Content-Type: lo imposta il browser con boundary
});
if (!res.ok) throw new Error('Upload fallito');
return res.json();
}
Stream di risposta: lettura progressiva
Nei browser che lo supportano, puoi leggere incrementi di dati con ReadableStream
.
const res = await fetch('/stream');
if (!res.ok) throw new Error('HTTP ' + res.status);
const reader = res.body.getReader();
const decoder = new TextDecoder();
let received = '';
while (true) {
const { value, done } = await reader.read();
if (done) break;
received += decoder.decode(value, { stream: true });
// aggiorna UI parziale qui
}
received += decoder.decode(); // flush finale
console.log(received);
Gestire avanzamento download
La Fetch API non espone eventi di progresso di alto livello, ma puoi derivarlo contando i byte letti dal reader (se il server invia Content-Length
).
const res = await fetch('/grande-file.bin');
const contentLength = Number(res.headers.get('Content-Length')) || 0;
const reader = res.body.getReader();
let loaded = 0;
while (true) {
const { done, value } = await reader.read();
if (done) break;
loaded += value.byteLength;
if (contentLength) {
console.log(`Progresso: ${Math.round(loaded / contentLength * 100)}%`);
}
}
Streaming upload (avanzato)
Alcuni browser supportano request body come ReadableStream
(e l’opzione sperimentale duplex: 'half'
in alcuni contesti). Verifica la compatibilità prima di usarla in produzione.
// Esempio concettuale: carica chunk generati al volo
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('parte1'));
controller.enqueue(new TextEncoder().encode('parte2'));
controller.close();
}
});
const res = await fetch('/upload-stream', {
method: 'POST',
body: stream,
// duplex potrebbe essere richiesto da alcuni browser:
// duplex: 'half'
});
Concorrenza: più richieste in parallelo
const ids = [1, 2, 3, 4, 5];
const promises = ids.map(id => fetch(`/items/${id}`).then(r => r.json()));
const results = await Promise.all(promises);
console.log(results);
Gestire errori singoli con Promise.allSettled
const endpoints = ['/a', '/b', '/c'];
const settled = await Promise.allSettled(
endpoints.map(u => fetch(u).then(r => r.json()))
);
for (const s of settled) {
if (s.status === 'fulfilled') console.log('OK:', s.value);
else console.warn('Errore:', s.reason);
}
Paginatione lato API
async function fetchAllPages(baseUrl) {
let next = baseUrl;
const items = [];
while (next) {
const res = await fetch(next);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const page = await res.json();
items.push(...page.data);
next = page.next; // URL della prossima pagina secondo il contratto API
}
return items;
}
Caching lato client
- Header HTTP: il server può controllare la cache con
Cache-Control
,ETag
,Last-Modified
. - Opzioni fetch:
cache
può forzare o bypassare la cache del browser.
// Bypassa cache
await fetch('/api/data', { cache: 'no-store' });
// Riutilizza da cache se possibile
await fetch('/api/data', { cache: 'force-cache' });
Gestire redirect e referrer
const res = await fetch('/go', { redirect: 'manual', referrerPolicy: 'no-referrer' });
if (res.type === 'opaqueredirect') {
// In manual potresti gestire l'URL Location se disponibile (limitazioni di sicurezza si applicano)
}
Autenticazione: token Bearer
const token = 'eyJhbGciOi...'; // conserva in memoria o Secure Storage, evita localStorage se possibile
const res = await fetch('/api/secure', {
headers: {
'Authorization': `Bearer ${token}`,
'Accept': 'application/json'
}
});
Inviare e ricevere JSON in sicurezza
- Imposta sempre
Accept: application/json
quando ti aspetti JSON. - Valida l’output del server prima di usarlo in UI.
- Gestisci i casi di
204 No Content
(niente body da leggere).
const res = await fetch('/api/maybe-empty');
if (res.status === 204) {
console.log('Nessun contenuto');
} else {
const data = await res.json();
console.log(data);
}
Inviare form classici (x-www-form-urlencoded)
const params = new URLSearchParams();
params.set('username', 'alice');
params.set('password', 'segretissima');
const res = await fetch('/login', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
keepalive e richieste in unload
keepalive: true
permette (entro un limite di dimensione) di inviare una richiesta quando la pagina sta per chiudersi, ad esempio per logging.
window.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
navigator.sendBeacon?.('/metrics', new Blob([JSON.stringify({ t: Date.now() })], { type: 'application/json' }));
// In alternativa, con fetch e keepalive (dimensioni limitate):
fetch('/metrics', {
method: 'POST',
body: JSON.stringify({ t: Date.now() }),
headers: { 'Content-Type': 'application/json' },
keepalive: true
});
}
});
Debug: ispezionare richieste e risposte
- Usa gli Strumenti Sviluppatore del browser: pannello Rete per header, payload, tempi.
- Logga sempre status, URL, timing e payload in fase di sviluppo.
async function loggedFetch(input, options) {
const started = performance.now();
try {
const res = await fetch(input, options);
console.info('FETCH', { url: res.url, status: res.status, ms: Math.round(performance.now() - started) });
return res;
} catch (err) {
console.error('FETCH ERR', { input, err, ms: Math.round(performance.now() - started) });
throw err;
}
}
Pattern utili in produzione
- Wrapper centralizzato per header comuni, token e gestione errori.
- Abort per richieste obsolete (es. digitazione in una searchbox).
- Cache applicativa (es. Map, IndexedDB) per evitare round trip ripetuti.
- Schema validation (es. con Zod) per dati esterni.
Note su compatibilità e ambienti
- Browser: Fetch è ampiamente supportata; verifica funzionalità avanzate (streaming upload) prima di usarle.
- Service Worker: puoi intercettare e rispondere a fetch per caching offline.
- Node.js: fetch è disponibile nativamente da Node 18+. Le API sono simili ma alcune opzioni differiscono.
Consigli
- Controlla sempre
response.ok
e gestisci errori. - Usa
async/await
etry/catch
per codice leggibile. - Imposta
Accept
eContent-Type
adeguati. - Usa
AbortController
per timeout e ricerche dinamiche. - Conserva i token in modo sicuro e inviali con
Authorization
solo quando necessario. - Valida sempre il JSON di risposta prima di mostrarlo in UI.
Conclusione
Con la Fetch API puoi coprire la quasi totalità dei casi d’uso di rete nel browser: dalle semplici GET al controllo fine di streaming, cache, aborti e autenticazione. Usa i pattern mostrati per costruire un livello di accesso robusto e mantenibile.